Files
ST-Bionic-Memory-Ecology/tests/graph-persistence.mjs

7385 lines
284 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from "node:assert/strict";
import {
buildBmeDbName,
buildGraphFromSnapshot,
buildPersistDelta,
buildPersistDeltaFromGraphDirtyState,
buildSnapshotFromGraph,
evaluateNativeHydrateGate,
evaluatePersistNativeDeltaGate,
} from "../sync/bme-db.js";
import { onMessageReceivedController } from "../host/event-binding.js";
import {
getBmeHostAdapter,
isBmeLightweightHostMode,
normalizeBmeChatStateTarget,
resolveBmeHostProfile,
resolveChatStateTargetChatId,
resolveCurrentBmeChatStateTarget,
serializeBmeChatStateTarget,
} from "../host/runtime-host-adapter.js";
import {
buildGraphCommitMarker,
buildGraphChatStateSnapshot,
buildLukerGraphCheckpointV2,
buildLukerGraphJournalEntry,
buildLukerGraphJournalV2,
buildLukerGraphManifestV2,
appendLukerGraphJournalEntryV2,
canUseGraphChatState,
detectIndexedDbSnapshotCommitMarkerMismatch,
deleteGraphChatStateNamespace,
cloneGraphForPersistence,
cloneRuntimeDebugValue,
findGraphShadowSnapshotByIntegrity,
GRAPH_CHAT_STATE_NAMESPACE,
getAcceptedCommitMarkerRevision,
getGraphPersistedRevision,
getGraphIdentityAliasCandidates,
getGraphPersistenceMeta,
GRAPH_COMMIT_MARKER_KEY,
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
LUKER_GRAPH_JOURNAL_COMPACTION_BYTES,
LUKER_GRAPH_JOURNAL_COMPACTION_DEPTH,
LUKER_GRAPH_JOURNAL_COMPACTION_REVISION_GAP,
LUKER_GRAPH_JOURNAL_NAMESPACE,
LUKER_GRAPH_MANIFEST_NAMESPACE,
getGraphShadowSnapshotStorageKey,
GRAPH_LOAD_PENDING_CHAT_ID,
GRAPH_IDENTITY_ALIAS_STORAGE_KEY,
GRAPH_LOAD_STATES,
GRAPH_METADATA_KEY,
GRAPH_PERSISTENCE_META_KEY,
GRAPH_PERSISTENCE_SESSION_ID,
GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX,
GRAPH_STARTUP_RECONCILE_DELAYS_MS,
MODULE_NAME,
normalizeGraphCommitMarker,
readGraphChatStateNamespaces,
readGraphCommitMarker,
readGraphChatStateSnapshot,
readLukerGraphSidecarV2,
readGraphShadowSnapshot,
replaceLukerGraphJournalV2,
rememberGraphIdentityAlias,
removeGraphShadowSnapshot,
resolveGraphIdentityAliasByHostChatId,
shouldPreferShadowSnapshotOverOfficial,
stampGraphPersistenceMeta,
writeChatMetadataPatch,
writeGraphChatStatePayload,
writeGraphChatStateSnapshot,
writeLukerGraphCheckpointV2,
writeLukerGraphManifestV2,
writeGraphShadowSnapshot,
} from "../graph/graph-persistence.js";
import {
createEmptyGraph,
deserializeGraph,
getGraphStats,
getNode,
serializeGraph,
updateNode,
} from "../graph/graph.js";
import {
buildPersistedRecallRecord,
bumpPersistedRecallGenerationCount,
readPersistedRecallFromUserMessage,
resolveFinalRecallInjectionSource,
writePersistedRecallToUserMessage,
} from "../retrieval/recall-persistence.js";
import { getNodeDisplayName } from "../graph/node-labels.js";
import {
buildVectorCollectionId,
hasGraphPersistDirtyState,
normalizeGraphRuntimeState,
pruneGraphPersistDirtyState,
} from "../runtime/runtime-state.js";
import {
defaultSettings,
getPersistedSettingsSnapshot,
mergePersistedSettings,
} from "../runtime/settings-defaults.js";
import {
areChatIdsEquivalentForIdentityCore,
canMutateRuntimeGraphForIdentityCore,
doesChatIdMatchIdentityCore,
planRuntimeGraphIdentityRepairCore,
resolveActiveHostChatIdCore,
resolveCurrentChatIdentityCore,
resolveGraphOwnerIdentityCore,
resolvePersistenceChatIdCore,
resolveRuntimeGraphFallbackIdentityCore,
} from "../runtime/identity-resolver.js";
import {
createDefaultAuthorityCapabilityState,
normalizeAuthoritySettings,
normalizeAuthorityCapabilityState,
probeAuthorityCapabilities,
} from "../runtime/authority-capabilities.js";
import { normalizeAuthorityJobConfig } from "../maintenance/authority-job-adapter.js";
import { normalizeAuthorityBlobConfig } from "../maintenance/authority-blob-adapter.js";
import {
createAuthorityBrowserState,
getAuthorityBrowserStateSnapshot,
normalizeAuthorityBrowserState,
recordAuthorityAcceptedRevision,
} from "../sync/authority-browser-state.js";
import {
AUTHORITY_GRAPH_STORE_KIND,
AUTHORITY_GRAPH_STORE_MODE,
AuthorityGraphStore,
} from "../sync/authority-graph-store.js";
import {
isAcceptedLegacyPersistenceTier,
isRecoveryOnlyLegacyPersistenceTier,
planAcceptedPendingPersistenceRepair,
repairLegacyLastBatchPersistenceStatus,
} from "../sync/legacy-persistence-repair.js";
import {
PERSISTENCE_EVENT_TYPES,
applyPersistenceRecordToBatchStatus as reducePersistenceRecordToBatchStatus,
buildAcceptedPersistenceStatePatch,
buildBatchPersistenceRecordFromPersistResult as reduceBatchPersistenceRecordFromPersistResult,
buildQueuedPersistenceStatePatch,
planAcceptedPendingClear,
reducePersistenceStatePatch,
} from "../sync/persistence-reducer.js";
import {
clampFloat,
clampInt,
createGraphPersistenceState,
createRecallInputRecord,
createRecallRunResult,
createUiStatus,
formatRecallContextLine,
getStageNoticeDuration,
getStageNoticeTitle,
hashRecallInput,
isFreshRecallInputRecord,
normalizeRecallInputText,
normalizeStageNoticeLevel,
shouldRunRecallForTransaction,
} from "../ui/ui-status.js";
function normalizeChatIdCandidate(value = "") {
return String(value ?? "").trim();
}
import { createRecallInputState } from "../runtime/recall-input-state.js";
import { createRerollRecallInput } from "../runtime/reroll-recall-input.js";
import { createGenerationRecallTransactions } from "../runtime/generation-recall-transactions.js";
import { createFinalRecallInjection } from "../runtime/final-recall-injection.js";
import { createAutoExtractionDefer } from "../runtime/auto-extraction-defer.js";
import { runPlannerRecallForEnaController } from "../runtime/planner-recall-controller.js";
import {
loadGraphFromIndexedDbImpl,
maybeFlushQueuedGraphPersistImpl,
queueGraphPersistToIndexedDbImpl,
retryPendingGraphPersistImpl,
saveGraphToIndexedDbImpl,
} from "../sync/graph-persistence-io.js";
import {
assertRecoveryChatStillActiveImpl,
applyGraphLoadStateImpl,
buildPanelOpenLocalStoreRefreshPlanImpl,
ensureGraphMutationReadyImpl,
getGraphMutationBlockReasonImpl,
getGraphPersistenceLiveStateImpl,
getPanelRuntimeStatusImpl,
readRuntimeDebugSnapshotImpl,
} from "../sync/graph-mutation-gate.js";
import {
buildBmeSyncRuntimeOptionsImpl,
loadGraphFromChatImpl,
maybeCaptureGraphShadowSnapshotImpl,
onRebuildLocalCacheFromLukerSidecarImpl,
persistExtractionBatchResultImpl,
saveGraphToChatImpl,
shouldUseAuthorityGraphStoreImpl,
shouldUseAuthorityJobsImpl,
syncGraphLoadFromLiveContextImpl,
writeAuthorityCheckpointFromCurrentGraphImpl,
} from "../sync/graph-load-persist.js";
function isAuthorityVectorConfig(config = null) {
return config?.mode === "authority" || config?.source === "authority-trivium";
}
function normalizeAuthorityVectorConfig(settings = {}, overrides = {}) {
return {
...overrides,
mode: "authority",
source: "authority-trivium",
baseUrl: String(settings?.authorityBaseUrl || overrides?.baseUrl || "/api/plugins/authority"),
};
}
function createAuthorityBlobAdapter() {
return {
async writeJson(path = "", payload = null) {
globalThis.__authorityBlobWrites?.set(String(path || ""), structuredClone(payload));
return { path, payload, written: true };
},
};
}
function createSessionStorage(seed = null) {
const store = seed instanceof Map ? seed : new Map();
return {
__store: store,
get length() {
return store.size;
},
key(index) {
return Array.from(store.keys())[Number(index)] ?? null;
},
getItem(key) {
return store.has(key) ? store.get(key) : null;
},
setItem(key, value) {
store.set(String(key), String(value));
},
removeItem(key) {
store.delete(String(key));
},
};
}
function createLocalStorage(seed = null) {
const store = seed instanceof Map ? seed : new Map();
return {
__store: store,
getItem(key) {
return store.has(key) ? store.get(key) : null;
},
setItem(key, value) {
store.set(String(key), String(value));
},
removeItem(key) {
store.delete(String(key));
},
};
}
function createMeaningfulGraph(chatId = "chat-test", suffix = "base") {
const graph = createEmptyGraph();
graph.historyState.chatId = chatId;
graph.historyState.extractionCount = 3;
graph.historyState.lastProcessedAssistantFloor = 6;
graph.lastProcessedSeq = 6;
graph.lastRecallResult = [{ id: `recall-${suffix}` }];
graph.nodes.push({
id: `node-${suffix}`,
type: "event",
fields: {
title: `事件-${suffix}`,
summary: `摘要-${suffix}`,
},
seq: 6,
seqRange: [6, 6],
archived: false,
embedding: null,
importance: 5,
accessCount: 0,
lastAccessTime: Date.now(),
createdTime: Date.now(),
level: 0,
parentId: null,
childIds: [],
prevId: null,
nextId: null,
clusters: [],
});
return normalizeGraphRuntimeState(graph, chatId);
}
function stampPersistedGraph(
graph,
{
revision = 1,
integrity = "",
chatId = graph?.historyState?.chatId || "",
reason = "test",
} = {},
) {
graph.__stBmePersistence = {
revision,
integrity,
chatId,
reason,
updatedAt: new Date().toISOString(),
sessionId: "test-session",
};
return graph;
}
async function createGraphPersistenceHarness({
chatId = "chat-test",
chatMetadata = undefined,
sessionStore = null,
localStore = null,
globalChatId = "",
characterId = "",
groupId = null,
indexedDbSnapshot = null,
indexedDbSnapshots = null,
chat = [],
} = {}) {
const timers = new Map();
let nextTimerId = 1;
const storage = createSessionStorage(sessionStore);
const sessionShadowSnapshots = sessionStore instanceof Map ? sessionStore : storage.__store;
const localStorage = createLocalStorage(localStore);
const indexedDbSnapshotMap =
indexedDbSnapshots instanceof Map
? new Map(indexedDbSnapshots)
: new Map(
Object.entries(
indexedDbSnapshots &&
typeof indexedDbSnapshots === "object" &&
!Array.isArray(indexedDbSnapshots)
? indexedDbSnapshots
: {},
),
);
if (indexedDbSnapshot) {
const primaryChatId = String(chatId || globalChatId || "");
if (primaryChatId) {
indexedDbSnapshotMap.set(primaryChatId, structuredClone(indexedDbSnapshot));
}
}
function buildEmptyIndexedDbSnapshot(targetChatId = "") {
return {
meta: { revision: 0, chatId: String(targetChatId || "") },
nodes: [],
edges: [],
tombstones: [],
state: { lastProcessedFloor: -1, extractionCount: 0 },
};
}
function getIndexedDbSnapshotForChat(targetChatId = "") {
const normalizedChatId = String(targetChatId || "");
if (normalizedChatId && indexedDbSnapshotMap.has(normalizedChatId)) {
return structuredClone(indexedDbSnapshotMap.get(normalizedChatId));
}
if (
normalizedChatId &&
indexedDbSnapshot &&
!indexedDbSnapshotMap.size &&
normalizedChatId === String(chatId || globalChatId || "")
) {
return structuredClone(indexedDbSnapshot);
}
return buildEmptyIndexedDbSnapshot(normalizedChatId);
}
function setIndexedDbSnapshotForChat(targetChatId = "", snapshot = null) {
const normalizedChatId = String(targetChatId || "");
if (!normalizedChatId) return;
if (!snapshot) {
indexedDbSnapshotMap.delete(normalizedChatId);
return;
}
indexedDbSnapshotMap.set(normalizedChatId, structuredClone(snapshot));
}
const authoritySnapshotMap = new Map();
const authorityBlobWrites = new Map();
globalThis.__authorityBlobWrites = authorityBlobWrites;
function getAuthoritySnapshotForChat(targetChatId = "") {
const normalizedChatId = String(targetChatId || "");
if (normalizedChatId && authoritySnapshotMap.has(normalizedChatId)) {
return structuredClone(authoritySnapshotMap.get(normalizedChatId));
}
return {
meta: {
revision: 0,
chatId: normalizedChatId,
storagePrimary: AUTHORITY_GRAPH_STORE_KIND,
storageMode: AUTHORITY_GRAPH_STORE_MODE,
},
nodes: [],
edges: [],
tombstones: [],
state: { lastProcessedFloor: -1, extractionCount: 0 },
};
}
function setAuthoritySnapshotForChat(targetChatId = "", snapshot = null) {
const normalizedChatId = String(targetChatId || "");
if (!normalizedChatId) return;
if (!snapshot) {
authoritySnapshotMap.delete(normalizedChatId);
return;
}
authoritySnapshotMap.set(normalizedChatId, structuredClone(snapshot));
}
class HarnessAuthorityGraphStore {
constructor(dbChatId = "") {
this.chatId = String(dbChatId || "");
this.storeKind = AUTHORITY_GRAPH_STORE_KIND;
this.storeMode = AUTHORITY_GRAPH_STORE_MODE;
}
async open() {}
async close() {}
getStorageDiagnosticsSync() {
return {
formatVersion: 1,
migrationState: "idle",
resolvedStoreMode: AUTHORITY_GRAPH_STORE_MODE,
storageKind: AUTHORITY_GRAPH_STORE_KIND,
browserCacheMode: "minimal",
};
}
async exportSnapshot() {
return getAuthoritySnapshotForChat(this.chatId);
}
async exportSnapshotProbe() {
return getAuthoritySnapshotForChat(this.chatId);
}
async importSnapshot(snapshot = {}, options = {}) {
const current = getAuthoritySnapshotForChat(this.chatId);
const currentRevision = Number(current?.meta?.revision || 0);
const incomingRevision = Number(snapshot?.meta?.revision || 0);
const requestedRevision = Number.isFinite(Number(options?.revision))
? Math.max(0, Math.floor(Number(options.revision)))
: options?.preserveRevision === true
? Math.max(0, Math.floor(incomingRevision))
: currentRevision + 1;
const nextRevision = Math.max(currentRevision + 1, requestedRevision);
const nowMs = Date.now();
const nodes = Array.isArray(snapshot?.nodes)
? snapshot.nodes.map((node) => structuredClone(node))
: [];
const edges = Array.isArray(snapshot?.edges)
? snapshot.edges.map((edge) => structuredClone(edge))
: [];
const tombstones = Array.isArray(snapshot?.tombstones)
? snapshot.tombstones.map((record) => structuredClone(record))
: [];
const nextSnapshot = {
meta: {
...(snapshot?.meta && typeof snapshot.meta === "object"
? structuredClone(snapshot.meta)
: {}),
chatId: this.chatId,
storagePrimary: AUTHORITY_GRAPH_STORE_KIND,
storageMode: AUTHORITY_GRAPH_STORE_MODE,
revision: nextRevision,
lastModified: nowMs,
lastMutationReason: "importSnapshot",
syncDirty: options?.markSyncDirty !== false,
syncDirtyReason: options?.markSyncDirty === false ? "" : "importSnapshot",
nodeCount: nodes.length,
edgeCount: edges.length,
tombstoneCount: tombstones.length,
},
nodes,
edges,
tombstones,
state:
snapshot?.state && typeof snapshot.state === "object"
? structuredClone(snapshot.state)
: { lastProcessedFloor: -1, extractionCount: 0 },
};
setAuthoritySnapshotForChat(this.chatId, nextSnapshot);
return {
mode: String(options?.mode || "replace"),
revision: nextRevision,
imported: {
nodes: nodes.length,
edges: edges.length,
tombstones: tombstones.length,
},
};
}
async importLegacyGraph(graph, options = {}) {
const revision = Math.max(1, Math.floor(Number(options?.revision) || 1));
const snapshot = buildSnapshotFromGraph(graph, {
chatId: this.chatId,
revision,
meta: {
migrationCompletedAt: Number(options?.nowMs || Date.now()),
migrationSource: String(options?.source || "chat_metadata"),
},
});
const importResult = await this.importSnapshot(snapshot, {
mode: "replace",
preserveRevision: true,
revision,
markSyncDirty: options?.markSyncDirty,
});
return {
migrated: true,
revision: importResult.revision,
imported: importResult.imported,
};
}
async commitDelta(delta = {}, options = {}) {
return commitSnapshotDelta({
targetChatId: this.chatId,
delta,
options,
getSnapshot: getAuthoritySnapshotForChat,
setSnapshot: setAuthoritySnapshotForChat,
metaPatch: {
storagePrimary: AUTHORITY_GRAPH_STORE_KIND,
storageMode: AUTHORITY_GRAPH_STORE_MODE,
},
});
}
async isEmpty() {
const snapshot = getAuthoritySnapshotForChat(this.chatId);
const nodes = Array.isArray(snapshot?.nodes) ? snapshot.nodes.length : 0;
const edges = Array.isArray(snapshot?.edges) ? snapshot.edges.length : 0;
const tombstones = Array.isArray(snapshot?.tombstones)
? snapshot.tombstones.length
: 0;
return {
empty: nodes === 0 && edges === 0,
nodes,
edges,
tombstones,
};
}
async getRevision() {
return Number(getAuthoritySnapshotForChat(this.chatId)?.meta?.revision || 0);
}
async getMeta(key, fallbackValue = 0) {
const snapshot = getAuthoritySnapshotForChat(this.chatId);
if (!snapshot?.meta || !(key in snapshot.meta)) {
return fallbackValue;
}
return snapshot.meta[key];
}
async patchMeta(record = {}) {
const snapshot = getAuthoritySnapshotForChat(this.chatId);
snapshot.meta = {
...(snapshot.meta || {}),
...(record && typeof record === "object" ? structuredClone(record) : {}),
};
setAuthoritySnapshotForChat(this.chatId, snapshot);
return record;
}
}
function commitSnapshotDelta({
targetChatId = "",
delta = {},
options = {},
getSnapshot,
setSnapshot,
metaPatch = {},
} = {}) {
const normalizedChatId = String(targetChatId || "");
const currentSnapshot =
typeof getSnapshot === "function" ? getSnapshot(normalizedChatId) : null;
const now = Date.now();
const nodeMap = new Map(
(Array.isArray(currentSnapshot?.nodes) ? currentSnapshot.nodes : [])
.filter((record) => record?.id)
.map((record) => [String(record.id), structuredClone(record)]),
);
const edgeMap = new Map(
(Array.isArray(currentSnapshot?.edges) ? currentSnapshot.edges : [])
.filter((record) => record?.id)
.map((record) => [String(record.id), structuredClone(record)]),
);
const tombstoneMap = new Map(
(Array.isArray(currentSnapshot?.tombstones) ? currentSnapshot.tombstones : [])
.filter((record) => record?.id)
.map((record) => [String(record.id), structuredClone(record)]),
);
for (const edgeId of Array.isArray(delta?.deleteEdgeIds) ? delta.deleteEdgeIds : []) {
edgeMap.delete(String(edgeId));
}
for (const nodeId of Array.isArray(delta?.deleteNodeIds) ? delta.deleteNodeIds : []) {
nodeMap.delete(String(nodeId));
}
for (const record of Array.isArray(delta?.upsertNodes) ? delta.upsertNodes : []) {
if (!record?.id) continue;
nodeMap.set(String(record.id), structuredClone(record));
}
for (const record of Array.isArray(delta?.upsertEdges) ? delta.upsertEdges : []) {
if (!record?.id) continue;
edgeMap.set(String(record.id), structuredClone(record));
}
for (const record of Array.isArray(delta?.tombstones) ? delta.tombstones : []) {
if (!record?.id) continue;
tombstoneMap.set(String(record.id), structuredClone(record));
}
const runtimeMetaPatch =
delta?.runtimeMetaPatch &&
typeof delta.runtimeMetaPatch === "object" &&
!Array.isArray(delta.runtimeMetaPatch)
? structuredClone(delta.runtimeMetaPatch)
: {};
const shouldMarkSyncDirty = options?.markSyncDirty !== false;
const nextRevision = Math.max(
Number(currentSnapshot?.meta?.revision || 0) + 1,
Number(options?.requestedRevision || 0),
);
const nextState = {
lastProcessedFloor: Number.isFinite(Number(runtimeMetaPatch.lastProcessedFloor))
? Number(runtimeMetaPatch.lastProcessedFloor)
: Number(currentSnapshot?.state?.lastProcessedFloor ?? -1),
extractionCount: Number.isFinite(Number(runtimeMetaPatch.extractionCount))
? Number(runtimeMetaPatch.extractionCount)
: Number(currentSnapshot?.state?.extractionCount ?? 0),
};
const nextSnapshot = {
meta: {
...(currentSnapshot?.meta || {}),
...runtimeMetaPatch,
...(metaPatch && typeof metaPatch === "object" ? structuredClone(metaPatch) : {}),
chatId: normalizedChatId,
revision: nextRevision,
lastModified: now,
lastMutationReason: String(options?.reason || "commitDelta"),
syncDirty: shouldMarkSyncDirty,
syncDirtyReason: shouldMarkSyncDirty
? String(options?.reason || "commitDelta")
: "",
nodeCount: nodeMap.size,
edgeCount: edgeMap.size,
tombstoneCount: tombstoneMap.size,
},
nodes: Array.from(nodeMap.values()),
edges: Array.from(edgeMap.values()),
tombstones: Array.from(tombstoneMap.values()),
state: nextState,
};
if (typeof setSnapshot === "function") {
setSnapshot(normalizedChatId, nextSnapshot);
}
return {
revision: nextRevision,
lastModified: now,
imported: {
nodes: nodeMap.size,
edges: edgeMap.size,
tombstones: tombstoneMap.size,
},
delta: {
upsertNodes: Array.isArray(delta?.upsertNodes) ? delta.upsertNodes.length : 0,
upsertEdges: Array.isArray(delta?.upsertEdges) ? delta.upsertEdges.length : 0,
deleteNodeIds: Array.isArray(delta?.deleteNodeIds) ? delta.deleteNodeIds.length : 0,
deleteEdgeIds: Array.isArray(delta?.deleteEdgeIds) ? delta.deleteEdgeIds.length : 0,
tombstones: Array.isArray(delta?.tombstones) ? delta.tombstones.length : 0,
},
};
}
function commitIndexedDbDelta(targetChatId = "", delta = {}, options = {}) {
const result = commitSnapshotDelta({
targetChatId,
delta,
options,
getSnapshot: getIndexedDbSnapshotForChat,
setSnapshot: setIndexedDbSnapshotForChat,
});
runtimeContext.__indexedDbSnapshot = getIndexedDbSnapshotForChat(
String(targetChatId || ""),
);
return result;
}
function buildPersistenceEnvironment(
context = null,
presentation = { storagePrimary: "indexeddb", storageMode: "indexeddb" },
) {
const hostProfile = resolveBmeHostProfile(context);
const primaryStorageTier = resolveBmeHostProfile(context) === "luker" ? "luker-chat-state" : String(presentation?.storagePrimary || "indexeddb");
const cacheStorageTier = primaryStorageTier === "authority" || primaryStorageTier === "luker-chat-state" ? "indexeddb" : "none";
return {
hostProfile,
primaryStorageTier,
cacheStorageTier,
};
}
let currentGraph = null;
let extractionCount = 0;
let lastExtractedItems = [];
let lastRecalledItems = [];
let lastInjectionContent = "";
let runtimeStatus = createUiStatus("待命", "准备就绪", "idle");
let lastExtractionStatus = createUiStatus("待命", "尚未执行提取", "idle");
let lastVectorStatus = createUiStatus("待命", "尚未执行向量任务", "idle");
let lastRecallStatus = createUiStatus("待命", "尚未执行召回", "idle");
let graphPersistenceState = createGraphPersistenceState();
let authorityCapabilityState = createDefaultAuthorityCapabilityState();
let authorityBrowserState = createAuthorityBrowserState();
let bmeLocalStoreCapabilitySnapshot = {
checked: false,
checkedAt: 0,
opfsAvailable: false,
reason: "unprobed",
};
let bmeChatManager = null;
let bmeChatManagerUnavailableWarned = false;
let nativeHydrateInstallPromise = null;
let nativePersistDeltaInstallPromise = null;
let pendingGraphLoadRetryTimer = null;
let pendingGraphLoadRetryChatId = "";
let pendingGraphPersistRetryTimer = null;
let pendingGraphPersistRetryChatId = "";
let pendingGraphPersistRetryAttempt = 0;
let pendingRecallSendIntent = createRecallInputRecord();
const bmeIndexedDbSnapshotCacheByChatId = new Map();
const bmeIndexedDbLatestQueuedRevisionByChatId = new Map();
const bmeIndexedDbWriteInFlightByChatId = new Map();
const GRAPH_LOAD_RETRY_DELAYS_MS = [120, 450, 1200, 2500];
const PENDING_GRAPH_PERSIST_RETRY_DELAYS_MS = [500, 1500, 5000];
const PENDING_GRAPH_PERSIST_MAX_RETRY_ATTEMPTS = 5;
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = new Set([
GRAPH_LOAD_STATES.LOADING,
GRAPH_LOAD_STATES.BLOCKED,
GRAPH_LOAD_STATES.NO_CHAT,
GRAPH_LOAD_STATES.SHADOW_RESTORED,
]);
const runtimeContext = {
console,
Date,
Math,
JSON,
Object,
Array,
String,
Number,
Boolean,
structuredClone,
result: null,
__indexedDbSnapshot: getIndexedDbSnapshotForChat(
String(chatId || globalChatId || ""),
),
__indexedDbSnapshots: indexedDbSnapshotMap,
__authoritySnapshots: authoritySnapshotMap,
__authorityBlobWrites: authorityBlobWrites,
__getAuthoritySnapshotForChat: getAuthoritySnapshotForChat,
__setAuthoritySnapshotForChat: setAuthoritySnapshotForChat,
sessionStorage: storage,
localStorage,
get currentGraph() { return currentGraph; },
set currentGraph(value) { currentGraph = value; },
get graphPersistenceState() { return graphPersistenceState; },
set graphPersistenceState(value) { graphPersistenceState = value; },
get bmeLocalStoreCapabilitySnapshot() { return bmeLocalStoreCapabilitySnapshot; },
set bmeLocalStoreCapabilitySnapshot(value) { bmeLocalStoreCapabilitySnapshot = value; },
get authorityCapabilityState() { return authorityCapabilityState; },
set authorityCapabilityState(value) { authorityCapabilityState = value; },
get authorityBrowserState() { return authorityBrowserState; },
set authorityBrowserState(value) { authorityBrowserState = value; },
extension_settings: {
[MODULE_NAME]: {},
},
defaultSettings,
getPersistedSettingsSnapshot,
mergePersistedSettings,
createDefaultAuthorityCapabilityState,
normalizeAuthoritySettings,
normalizeAuthorityCapabilityState,
normalizeAuthorityJobConfig,
probeAuthorityCapabilities,
isAuthorityVectorConfig,
normalizeAuthorityVectorConfig,
normalizeAuthorityBlobConfig,
createAuthorityBlobAdapter,
createAuthorityBrowserState,
getAuthorityBrowserStateSnapshot,
normalizeAuthorityBrowserState,
recordAuthorityAcceptedRevision,
AUTHORITY_GRAPH_STORE_KIND,
AUTHORITY_GRAPH_STORE_MODE,
AUTHORITY_DIAGNOSTICS_MANIFEST_LIMIT: 20,
AuthorityGraphStore: HarnessAuthorityGraphStore,
isAcceptedLegacyPersistenceTier,
isRecoveryOnlyLegacyPersistenceTier,
planAcceptedPendingPersistenceRepair,
repairLegacyLastBatchPersistenceStatus,
PERSISTENCE_EVENT_TYPES,
reducePersistenceRecordToBatchStatus,
buildAcceptedPersistenceStatePatch,
reduceBatchPersistenceRecordFromPersistResult,
buildQueuedPersistenceStatePatch,
planAcceptedPendingClear,
reducePersistenceStatePatch,
migrateLegacyTaskProfiles(settings = {}) {
return {
taskProfilesVersion: Number(settings?.taskProfilesVersion || 0),
taskProfiles:
settings?.taskProfiles && typeof settings.taskProfiles === "object"
? settings.taskProfiles
: {},
};
},
migratePerTaskRegexToGlobal(settings = {}) {
return {
changed: false,
settings,
};
},
setTimeout(fn, delay) {
const id = nextTimerId++;
timers.set(id, { fn, delay });
return id;
},
clearTimeout(id) {
timers.delete(id);
},
queueMicrotask(fn) {
fn();
},
toastr: {
info() {},
warning() {},
error() {},
success() {},
},
window: {
addEventListener() {},
removeEventListener() {},
},
document: {
visibilityState: "visible",
getElementById() {
return null;
},
},
createRecallInputState,
createRerollRecallInput,
createGenerationRecallTransactions,
createFinalRecallInjection,
createAutoExtractionDefer,
runPlannerRecallForEnaController,
loadGraphFromIndexedDbImpl,
maybeFlushQueuedGraphPersistImpl,
queueGraphPersistToIndexedDbImpl,
retryPendingGraphPersistImpl,
saveGraphToIndexedDbImpl,
assertRecoveryChatStillActiveImpl,
buildPanelOpenLocalStoreRefreshPlanImpl,
ensureGraphMutationReadyImpl,
getGraphMutationBlockReasonImpl,
getGraphPersistenceLiveStateImpl,
getPanelRuntimeStatusImpl,
readRuntimeDebugSnapshotImpl,
buildBmeSyncRuntimeOptionsImpl,
loadGraphFromChatImpl,
maybeCaptureGraphShadowSnapshotImpl,
onRebuildLocalCacheFromLukerSidecarImpl,
persistExtractionBatchResultImpl,
saveGraphToChatImpl,
shouldUseAuthorityGraphStoreImpl,
shouldUseAuthorityJobsImpl,
syncGraphLoadFromLiveContextImpl,
writeAuthorityCheckpointFromCurrentGraphImpl,
createRecallMessageUiController() {
return {
refreshPersistedRecallMessageUi: () => ({
status: "missing_recall_record",
renderedCount: 0,
persistedRecordCount: 0,
waitingMessageIndices: [],
anchorFailureIndices: [],
skippedNonUserIndices: [],
}),
schedulePersistedRecallMessageUiRefresh() {},
cleanupPersistedRecallMessageUi() {},
resolveMessageIndexFromElement: () => null,
resolveRecallCardAnchor: () => null,
};
},
openRecallSidebar() {},
removePersistedRecallFromUserMessage: () => false,
writePersistedRecallToUserMessage: () => false,
buildPersistedRecallRecord: (record = {}) => ({ ...record }),
markPersistedRecallManualEdit: () => null,
createRecallCardElement: () => null,
updateRecallCardData() {},
estimateTokens: (text = "") =>
String(text || "").trim().split(/\s+/).filter(Boolean).length || 1,
SillyTavern: {
getCurrentChatId() {
return runtimeContext.__globalChatId;
},
},
__globalChatId: String(globalChatId || ""),
Dexie: {
async exists(dbName = "") {
return Array.from(indexedDbSnapshotMap.keys()).some(
(candidateChatId) => buildBmeDbName(candidateChatId) === String(dbName),
);
},
async getDatabaseNames() {
return Array.from(indexedDbSnapshotMap.keys()).map((candidateChatId) =>
buildBmeDbName(candidateChatId),
);
},
},
async ensureDexieLoaded() {
return runtimeContext.Dexie;
},
refreshPanelLiveState() {
runtimeContext.__panelRefreshCount += 1;
},
schedulePersistedRecallMessageUiRefresh() {},
restoreRecallUiStateFromPersistence(chat = runtimeContext.getContext()?.chat) {
let latestPersisted = null;
if (Array.isArray(chat)) {
for (let index = chat.length - 1; index >= 0; index--) {
if (!chat[index]?.is_user) continue;
const record = readPersistedRecallFromUserMessage(chat, index);
if (record?.injectionText) {
latestPersisted = { messageIndex: index, record };
break;
}
}
}
const normalizeRecallNodeIdList = (nodeIds = []) => Array.isArray(nodeIds)
? nodeIds.map((entry) => {
if (typeof entry === "string" || typeof entry === "number") return String(entry).trim();
if (entry && typeof entry === "object") return String(entry.id || entry.nodeId || "").trim();
return "";
}).filter(Boolean)
: [];
const graphRecallNodeIds = normalizeRecallNodeIdList(currentGraph?.lastRecallResult);
const persistedNodeIds = normalizeRecallNodeIdList(latestPersisted?.record?.selectedNodeIds);
const effectiveNodeIds = graphRecallNodeIds.length ? graphRecallNodeIds : persistedNodeIds;
lastRecalledItems = effectiveNodeIds.map((id) => ({ id }));
lastInjectionContent = String(latestPersisted?.record?.injectionText || "").trim();
return {
restored: Boolean(lastInjectionContent || effectiveNodeIds.length),
latestPersistedMessageIndex: Number.isFinite(latestPersisted?.messageIndex) ? latestPersisted.messageIndex : null,
selectedNodeIds: effectiveNodeIds,
injectionTextLength: lastInjectionContent.length,
};
},
scheduleGraphChatStateProbe() {
return false;
},
scheduleIndexedDbGraphProbe() {
return false;
},
canUseHostGraphChatStatePersistence(context = runtimeContext.getContext()) {
return runtimeContext.resolveBmeHostProfile(context) === "luker" || canUseGraphChatState(context);
},
async refreshRuntimeGraphAfterSyncApplied(syncPayload = {}) {
const action = String(syncPayload?.action || "").trim().toLowerCase();
if (action !== "download" && action !== "merge" && action !== "restore-backup") return { refreshed: false, reason: "action-not-supported", action };
const syncedChatId = normalizeChatIdCandidate(syncPayload?.chatId);
const activeIdentity = runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext());
const activeChatId = normalizeChatIdCandidate(activeIdentity.chatId);
const targetChatId = activeChatId && syncedChatId && runtimeContext.doesChatIdMatchResolvedGraphIdentity(syncedChatId, activeIdentity) ? activeChatId : syncedChatId || activeChatId;
if (!targetChatId) return { refreshed: false, reason: "missing-chat-id", action };
if (activeChatId && targetChatId !== activeChatId) return { refreshed: false, reason: "chat-switched", action, chatId: targetChatId, activeChatId };
const loadResult = await runtimeContext.loadGraphFromIndexedDb(targetChatId, { source: `sync-post-refresh:${action}`, allowOverride: true, applyEmptyState: true });
return { refreshed: Boolean(loadResult?.loaded || loadResult?.emptyConfirmed), action, chatId: targetChatId, ...loadResult };
},
getGraphPersistenceState() { return graphPersistenceState; },
getBmeLocalStoreCapabilitySnapshot() { return bmeLocalStoreCapabilitySnapshot; },
getCurrentGraph() { return currentGraph; },
setCurrentGraph(graph) { currentGraph = graph; return currentGraph; },
getExtractionCount() { return extractionCount; },
setExtractionCount(value) { extractionCount = Number(value) || 0; return extractionCount; },
getLastExtractedItems() { return lastExtractedItems; },
setLastExtractedItems(items) { lastExtractedItems = Array.isArray(items) ? items : []; return lastExtractedItems; },
getLastRecalledItems() { return lastRecalledItems; },
setLastRecalledItems(items) { lastRecalledItems = Array.isArray(items) ? items : []; return lastRecalledItems; },
getLastInjectionContent() { return lastInjectionContent; },
setLastInjectionContent(content) { lastInjectionContent = String(content || ""); return lastInjectionContent; },
getRuntimeStatus() { return runtimeStatus; },
setRuntimeStatus(status) { runtimeStatus = status; return runtimeStatus; },
getLastExtractionStatus() { return lastExtractionStatus; },
setLastExtractionStatus(status) { lastExtractionStatus = status; return lastExtractionStatus; },
getLastVectorStatus() { return lastVectorStatus; },
setLastVectorStatus(status) { lastVectorStatus = status; return lastVectorStatus; },
getLastRecallStatus() { return lastRecallStatus; },
setLastRecallStatus(status) { lastRecallStatus = status; return lastRecallStatus; },
__panelRefreshCount: 0,
getLastProcessedAssistantFloor() {
const historyFloor = Number(
runtimeContext.currentGraph?.historyState?.lastProcessedAssistantFloor,
);
if (Number.isFinite(historyFloor)) {
return historyFloor;
}
const legacySeq = Number(runtimeContext.currentGraph?.lastProcessedSeq);
if (Number.isFinite(legacySeq)) return legacySeq;
return -1;
},
createEmptyGraph,
normalizeGraphRuntimeState,
serializeGraph,
deserializeGraph,
getGraphStats,
getNode,
getNodeDisplayName,
createUiStatus,
createGraphPersistenceState,
createRecallInputRecord,
createRecallRunResult,
getPendingRecallSendIntent() { return null; },
setPendingRecallSendIntent() {},
consumeCurrentGenerationTrivialSkip() { return false; },
resolveAutoExtractionPlan() { return { canRun: false, reason: "test-disabled", strategy: "normal" }; },
deferAutoExtraction() { return null; },
maybeResumePendingAutoExtraction() { return null; },
async persistGraphToHostChatState(context = runtimeContext.__chatContext, options = {}) {
const graph = options?.graph || currentGraph;
const revision = Math.max(1, Math.floor(Number(options?.revision || 0)));
const storageTier = String(options?.storageTier || "chat-state");
const target = options?.chatStateTarget || null;
if (storageTier === "chat-state") {
const result = await writeGraphChatStateSnapshot(context, graph, {
revision,
storageTier,
accepted: options?.accepted !== false,
reason: options?.reason || "",
chatId: options?.chatId || graph?.historyState?.chatId || runtimeContext.getCurrentChatId(),
integrity: runtimeContext.getChatMetadataIntegrity(context),
lastProcessedAssistantFloor: options?.lastProcessedAssistantFloor,
extractionCount: options?.extractionCount,
target,
});
runtimeContext.updateGraphPersistenceState({ dualWriteLastResult: { target: "chat-state" } });
return { saved: result?.ok === true, accepted: result?.ok === true, revision, reason: result?.reason || "chat-state-saved" };
}
const chatIdValue = options?.chatId || graph?.historyState?.chatId || runtimeContext.getCurrentChatId();
const integrity = runtimeContext.getChatMetadataIntegrity(context);
const checkpoint = buildLukerGraphCheckpointV2(graph, { revision, chatId: chatIdValue, integrity, reason: options?.reason || "", storageTier });
await context.updateChatState(LUKER_GRAPH_CHECKPOINT_NAMESPACE, () => checkpoint, target ? { target } : undefined);
const existingManifest = await context.getChatState(LUKER_GRAPH_MANIFEST_NAMESPACE, target ? { target } : undefined);
let nextRevision = revision;
if (options?.persistDelta && existingManifest?.headRevision) nextRevision = Number(existingManifest.headRevision || 0) + 1;
else if (options?.persistDelta) nextRevision = 2;
if (options?.reason === "luker-bootstrap-journal-fail") {
const journalResult = await context.updateChatState(LUKER_GRAPH_JOURNAL_NAMESPACE, () => ({ entries: [] }), target ? { target } : undefined);
if (journalResult?.ok !== true) return { saved: false, accepted: false, revision };
} else if (options?.persistDelta) {
const entry = buildLukerGraphJournalEntry(options?.persistDelta || null, { revision: nextRevision, reason: options?.reason || "", storageTier, chatId: chatIdValue, integrity });
await appendLukerGraphJournalEntryV2(context, entry, { chatId: chatIdValue, integrity, chatStateTarget: target });
}
if (!options?.persistDelta) await context.updateChatState(LUKER_GRAPH_JOURNAL_NAMESPACE, () => buildLukerGraphJournalV2([], { chatId: chatIdValue, integrity, headRevision: nextRevision }), target ? { target } : undefined);
const manifest = buildLukerGraphManifestV2(graph, { headRevision: nextRevision, checkpointRevision: revision, journalDepth: options?.persistDelta ? 1 : 0, chatId: chatIdValue, integrity, reason: options?.reason || "", storageTier, accepted: options?.accepted !== false, lastProcessedAssistantFloor: options?.lastProcessedAssistantFloor, extractionCount: options?.extractionCount });
await writeLukerGraphManifestV2(context, manifest, { chatStateTarget: target });
return { saved: true, accepted: options?.accepted !== false, revision: nextRevision };
},
getIsHostGenerationRunning() { return false; },
refreshPersistedRecallMessageUi() {},
normalizeStageNoticeLevel,
getStageNoticeTitle,
getStageNoticeDuration,
normalizeRecallInputText,
hashRecallInput,
isFreshRecallInputRecord,
clampInt,
clampFloat,
formatRecallContextLine,
getBmeHostAdapter(context = null) {
const activeContext = context || runtimeContext.__chatContext || {};
return {
context: activeContext,
hostProfile: runtimeContext.resolveBmeHostProfile(activeContext),
resolveCurrentTarget(options = {}) {
return runtimeContext.resolveCurrentBmeChatStateTarget(
activeContext,
options?.target,
);
},
getChatIdFromTarget(target = null) {
return runtimeContext.resolveChatStateTargetChatId(target);
},
isLightweightHostMode() {
return runtimeContext.isBmeLightweightHostMode(activeContext);
},
};
},
isBmeLightweightHostMode(context = null) {
return runtimeContext.resolveBmeHostProfile(context) === "luker";
},
normalizeBmeChatStateTarget,
resolveBmeHostProfile(context = null) {
const activeContext = context || runtimeContext.__chatContext || {};
const hasImplicitCurrentChat =
String(activeContext?.chatId || "").trim() ||
String(activeContext?.groupId || "").trim() ||
String(activeContext?.characterId || "").trim();
return runtimeContext.Luker &&
typeof runtimeContext.Luker?.getContext === "function" &&
hasImplicitCurrentChat
? "luker"
: "generic-st";
},
resolveChatStateTargetChatId(target = null) {
return resolveChatStateTargetChatId(target);
},
resolveCurrentBmeChatStateTarget(context = null, explicitTarget = null) {
if (explicitTarget) {
return normalizeBmeChatStateTarget(explicitTarget);
}
const activeContext = context || runtimeContext.__chatContext || {};
if (String(activeContext?.groupId || "").trim()) {
return {
is_group: true,
id: String(activeContext.chatId || activeContext.groupId).trim(),
};
}
const avatar =
activeContext?.characterAvatar ||
activeContext?.avatar_url ||
activeContext?.characters?.[activeContext?.characterId]?.avatar ||
activeContext?.characters?.[Number(activeContext?.characterId)]?.avatar ||
"";
const fileName = String(activeContext?.chatId || "").trim();
if (avatar && fileName) {
return {
is_group: false,
avatar_url: String(avatar),
file_name: fileName,
};
}
return null;
},
serializeBmeChatStateTarget(target = null) {
return serializeBmeChatStateTarget(target);
},
readPersistedRecallFromUserMessage,
writePersistedRecallToUserMessage,
bumpPersistedRecallGenerationCount,
resolveFinalRecallInjectionSource,
formatInjection: (result = null) =>
String(result?.injectionText || result?.memoryBlock || ""),
getSchema: () => [],
shouldRunRecallForTransaction,
areChatIdsEquivalentForIdentityCore,
cloneGraphForPersistence,
canMutateRuntimeGraphForIdentityCore,
buildGraphCommitMarker,
buildGraphChatStateSnapshot,
buildLukerGraphCheckpointV2,
buildLukerGraphJournalEntry,
buildLukerGraphJournalV2,
buildLukerGraphManifestV2,
canUseGraphChatState,
cloneRuntimeDebugValue,
deleteGraphChatStateNamespace,
doesChatIdMatchIdentityCore,
detectIndexedDbSnapshotCommitMarkerMismatch,
onMessageReceivedController,
GRAPH_CHAT_STATE_NAMESPACE,
getAcceptedCommitMarkerRevision,
getGraphPersistenceMeta,
getGraphPersistedRevision,
getGraphIdentityAliasCandidates,
GRAPH_COMMIT_MARKER_KEY,
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
LUKER_GRAPH_JOURNAL_COMPACTION_BYTES,
LUKER_GRAPH_JOURNAL_COMPACTION_DEPTH,
LUKER_GRAPH_JOURNAL_COMPACTION_REVISION_GAP,
LUKER_GRAPH_JOURNAL_NAMESPACE,
LUKER_GRAPH_MANIFEST_NAMESPACE,
getGraphShadowSnapshotStorageKey,
GRAPH_IDENTITY_ALIAS_STORAGE_KEY,
GRAPH_LOAD_PENDING_CHAT_ID,
GRAPH_LOAD_STATES,
GRAPH_METADATA_KEY,
GRAPH_PERSISTENCE_META_KEY,
GRAPH_PERSISTENCE_SESSION_ID,
GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX,
GRAPH_STARTUP_RECONCILE_DELAYS_MS,
MODULE_NAME,
planRuntimeGraphIdentityRepairCore,
findGraphShadowSnapshotByIntegrity,
normalizeGraphCommitMarker,
readGraphChatStateNamespaces,
readGraphCommitMarker,
readGraphChatStateSnapshot,
readLukerGraphSidecarV2,
readGraphShadowSnapshot,
rememberGraphIdentityAlias,
removeGraphShadowSnapshot,
resolveActiveHostChatIdCore,
resolveCurrentChatIdentityCore,
resolveGraphOwnerIdentityCore,
resolvePersistenceChatIdCore,
resolveRuntimeGraphFallbackIdentityCore,
resolveGraphIdentityAliasByHostChatId,
shouldPreferShadowSnapshotOverOfficial,
stampGraphPersistenceMeta,
replaceLukerGraphJournalV2,
appendLukerGraphJournalEntryV2,
writeChatMetadataPatch,
writeGraphChatStatePayload,
writeGraphChatStateSnapshot,
writeLukerGraphManifestV2,
writeLukerGraphCheckpointV2,
writeGraphShadowSnapshot,
// Shadow snapshot functions need VM-local sessionStorage overrides
// because imported versions use the outer globalThis (no sessionStorage)
rememberGraphIdentityAlias({
integrity = "",
hostChatId = "",
persistenceChatId = "",
} = {}) {
const normalizedIntegrity = String(integrity || "").trim();
if (!normalizedIntegrity) return null;
const normalizedHostChatId = String(hostChatId || "").trim();
const normalizedPersistenceChatId = String(
persistenceChatId || normalizedIntegrity,
).trim();
let registry = { byIntegrity: {} };
try {
const raw = localStorage.getItem(GRAPH_IDENTITY_ALIAS_STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (
parsed?.byIntegrity &&
typeof parsed.byIntegrity === "object" &&
!Array.isArray(parsed.byIntegrity)
) {
registry = { byIntegrity: parsed.byIntegrity };
}
}
} catch {
registry = { byIntegrity: {} };
}
const current = registry.byIntegrity[normalizedIntegrity] || {};
const hostChatIds = Array.from(
new Set(
[
normalizedHostChatId,
...(Array.isArray(current.hostChatIds) ? current.hostChatIds : []),
].filter(Boolean),
),
);
const next = {
integrity: normalizedIntegrity,
persistenceChatId: normalizedPersistenceChatId,
hostChatIds,
updatedAt: new Date().toISOString(),
};
registry.byIntegrity[normalizedIntegrity] = next;
localStorage.setItem(
GRAPH_IDENTITY_ALIAS_STORAGE_KEY,
JSON.stringify(registry),
);
return next;
},
resolveGraphIdentityAliasByHostChatId(hostChatId = "") {
const normalizedHostChatId = String(hostChatId || "").trim();
if (!normalizedHostChatId) return "";
try {
const raw = localStorage.getItem(GRAPH_IDENTITY_ALIAS_STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : { byIntegrity: {} };
let best = "";
let bestUpdatedAt = "";
for (const value of Object.values(parsed.byIntegrity || {})) {
const hostChatIds = Array.isArray(value?.hostChatIds)
? value.hostChatIds.map((item) => String(item || "").trim())
: [];
if (!hostChatIds.includes(normalizedHostChatId)) continue;
const persistenceChatId = String(
value?.persistenceChatId || value?.integrity || "",
).trim();
if (!persistenceChatId) continue;
const updatedAt = String(value?.updatedAt || "");
if (!best || updatedAt > bestUpdatedAt) {
best = persistenceChatId;
bestUpdatedAt = updatedAt;
}
}
return best;
} catch {
return "";
}
},
getGraphIdentityAliasCandidates({
integrity = "",
hostChatId = "",
persistenceChatId = "",
} = {}) {
const normalizedIntegrity = String(integrity || "").trim();
const normalizedHostChatId = String(hostChatId || "").trim();
const normalizedPersistenceChatId = String(
persistenceChatId || "",
).trim();
const candidates = [];
const seen = new Set();
const addCandidate = (value) => {
const normalized = String(value || "").trim();
if (!normalized || seen.has(normalized)) return;
seen.add(normalized);
candidates.push(normalized);
};
try {
const raw = localStorage.getItem(GRAPH_IDENTITY_ALIAS_STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : { byIntegrity: {} };
if (normalizedIntegrity) {
const value = parsed.byIntegrity?.[normalizedIntegrity] || {};
addCandidate(value?.persistenceChatId || value?.integrity || "");
for (const candidate of Array.isArray(value?.hostChatIds)
? value.hostChatIds
: []) {
addCandidate(candidate);
}
} else if (normalizedHostChatId) {
addCandidate(
runtimeContext.resolveGraphIdentityAliasByHostChatId(
normalizedHostChatId,
),
);
}
} catch {
// ignore
}
addCandidate(normalizedHostChatId);
addCandidate(normalizedPersistenceChatId);
return candidates;
},
readGraphShadowSnapshot(chatId = "") {
if (String(chatId || "") === "chat-official" && globalThis.__returnOfficialShadow === true) {
return { chatId: "chat-official", revision: 3, serializedGraph: serializeGraph(createMeaningfulGraph("chat-official", "shadow-stale")), updatedAt: new Date().toISOString(), reason: "stale-shadow", integrity: "", persistedChatId: "", debugReason: "stale-shadow" };
}
const key = getGraphShadowSnapshotStorageKey(chatId);
if (!key) return null;
try {
let raw = storage.getItem(key);
if (!raw && sessionShadowSnapshots?.has?.(`shadow-json:${String(chatId || "")}`)) raw = sessionShadowSnapshots.get(`shadow-json:${String(chatId || "")}`);
if (!raw) {
const rawGraph = sessionShadowSnapshots?.get?.(`shadow-raw:${String(chatId || "")}`);
return rawGraph
? { chatId: String(chatId || ""), revision: 0, serializedGraph: serializeGraph(rawGraph), updatedAt: new Date().toISOString(), reason: "stale-shadow", integrity: "", persistedChatId: "", debugReason: "stale-shadow" }
: null;
}
const snap = JSON.parse(raw);
if (!snap || String(snap.chatId || "") !== String(chatId || "") || typeof snap.serializedGraph !== "string" || !snap.serializedGraph) return null;
return {
chatId: String(snap.chatId || ""),
revision: Number.isFinite(snap.revision) ? snap.revision : 0,
serializedGraph: snap.serializedGraph,
updatedAt: String(snap.updatedAt || ""),
reason: String(snap.reason || ""),
integrity: String(snap.integrity || ""),
persistedChatId: String(snap.persistedChatId || ""),
debugReason: String(snap.debugReason || snap.reason || ""),
};
} catch {
const rawGraph = sessionShadowSnapshots?.get?.(`shadow-raw:${String(chatId || "")}`);
return rawGraph
? { chatId: String(chatId || ""), revision: 0, serializedGraph: serializeGraph(rawGraph), updatedAt: new Date().toISOString(), reason: "stale-shadow", integrity: "", persistedChatId: "", debugReason: "stale-shadow" }
: null;
}
},
findGraphShadowSnapshotByIntegrity(integrity = "", { excludeChatIds = [] } = {}) {
const normalizedIntegrity = String(integrity || "").trim();
if (!normalizedIntegrity) return null;
const excluded = new Set(
(Array.isArray(excludeChatIds) ? excludeChatIds : [])
.map((value) => String(value || "").trim())
.filter(Boolean),
);
let best = null;
for (const key of storage.__store.keys()) {
if (!String(key || "").startsWith(GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX)) {
continue;
}
try {
const snap = JSON.parse(storage.getItem(key));
if (
!snap ||
String(snap.integrity || "") !== normalizedIntegrity ||
typeof snap.serializedGraph !== "string" ||
!snap.serializedGraph
) {
continue;
}
const normalizedChatId = String(snap.chatId || "").trim();
if (!normalizedChatId || excluded.has(normalizedChatId)) {
continue;
}
if (
!best ||
Number(snap.revision || 0) > Number(best.revision || 0) ||
(Number(snap.revision || 0) === Number(best.revision || 0) &&
String(snap.updatedAt || "") > String(best.updatedAt || ""))
) {
best = {
chatId: normalizedChatId,
revision: Number.isFinite(snap.revision) ? snap.revision : 0,
serializedGraph: snap.serializedGraph,
updatedAt: String(snap.updatedAt || ""),
reason: String(snap.reason || ""),
integrity: String(snap.integrity || ""),
persistedChatId: String(snap.persistedChatId || ""),
debugReason: String(snap.debugReason || snap.reason || ""),
};
}
} catch {
// ignore
}
}
return best;
},
writeGraphShadowSnapshot(
chatId = "",
graph = null,
{ revision = 0, reason = "", integrity = "", debugReason = "" } = {},
) {
const key = getGraphShadowSnapshotStorageKey(chatId);
if (!key || !graph) return false;
sessionShadowSnapshots?.set?.(`shadow-raw:${String(chatId || "")}`, structuredClone(graph));
const persistedMeta = getGraphPersistenceMeta(graph) || {};
try {
const payload = JSON.stringify({
chatId: String(chatId || ""),
revision: Number.isFinite(revision) ? revision : 0,
serializedGraph: serializeGraph(graph),
updatedAt: new Date().toISOString(),
reason: String(reason || ""),
integrity: String(integrity || persistedMeta.integrity || ""),
persistedChatId: String(persistedMeta.chatId || ""),
debugReason: String(debugReason || reason || ""),
});
sessionShadowSnapshots?.set?.(`shadow-json:${String(chatId || "")}`, payload);
storage.setItem(key, payload);
return true;
} catch {
return false;
}
},
removeGraphShadowSnapshot(chatId = "") {
const key = getGraphShadowSnapshotStorageKey(chatId);
if (!key) return false;
try {
storage.removeItem(key);
sessionShadowSnapshots?.delete?.(`shadow-json:${String(chatId || "")}`);
sessionShadowSnapshots?.delete?.(`shadow-raw:${String(chatId || "")}`);
return true;
} catch {
return false;
}
},
createDefaultTaskProfiles() {
return {
extract: { activeProfileId: "default", profiles: [] },
recall: { activeProfileId: "default", profiles: [] },
compress: { activeProfileId: "default", profiles: [] },
synopsis: { activeProfileId: "default", profiles: [] },
reflection: { activeProfileId: "default", profiles: [] },
};
},
getContext() {
return runtimeContext.__chatContext;
},
async saveMetadata() {
runtimeContext.__globalImmediateSaveCalls += 1;
},
saveMetadataDebounced() {
runtimeContext.__globalSaveCalls += 1;
},
__globalSaveCalls: 0,
__globalImmediateSaveCalls: 0,
isAssistantChatMessage() {
return false;
},
isFreshRecallInputRecord() {
return true;
},
notifyExtractionIssue() {},
debugDebug() {},
debugLog() {},
async runExtraction() {},
getRequestHeaders() {
return {};
},
recordAuthorityBlobSnapshot() {},
isGraphLoadStateDbReady(loadState = runtimeContext.graphPersistenceState?.loadState) {
return loadState === GRAPH_LOAD_STATES.LOADED || loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED;
},
isLukerPrimaryPersistenceHost(context = runtimeContext.getContext?.()) {
return resolveBmeHostProfile(context) === "luker";
},
async loadGraphFromLukerSidecarV2(chatId, options = {}) {
runtimeContext.__lastLukerSidecarLoad = { chatId, options };
return { loaded: Boolean(runtimeContext.currentGraph), reason: "test-luker-sidecar" };
},
resolvePersistRevisionFloor(baseRevision = 0, graph = runtimeContext.currentGraph) {
return Math.max(
0,
Math.floor(Number(baseRevision || 0)),
Math.floor(Number(graph?.meta?.revision || 0)),
Math.floor(Number(getGraphPersistedRevision(graph) || 0)),
);
},
allocateRequestedPersistRevision(baseRevision = 0, graph = runtimeContext.currentGraph) {
return Math.max(
runtimeContext.resolvePersistRevisionFloor(baseRevision, graph) + 1,
Number(graphPersistenceState.revision || 0) + 1,
);
},
resolveCurrentChatStateTarget(context = runtimeContext.getContext?.()) {
return resolveCurrentBmeChatStateTarget(context);
},
scheduleBmeIndexedDbTask(task) {
return Promise.resolve().then(() => task());
},
__syncNowCalls: [],
getSettings() {
const mergedSettings = mergePersistedSettings(runtimeContext.extension_settings[MODULE_NAME] || {});
runtimeContext.extension_settings[MODULE_NAME] = mergedSettings;
return mergedSettings;
},
bmeIndexedDbLatestQueuedRevisionByChatId,
bmeIndexedDbWriteInFlightByChatId,
normalizeChatIdCandidate,
applyPersistDeltaToSnapshot(snapshot = null, delta = null, options = {}) {
const baseSnapshot = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)
? cloneRuntimeDebugValue(snapshot, snapshot)
: { meta: {}, state: { lastProcessedFloor: -1, extractionCount: 0 }, nodes: [], edges: [], tombstones: [] };
const normalizedDelta = delta && typeof delta === "object" && !Array.isArray(delta)
? cloneRuntimeDebugValue(delta, delta)
: {};
const nodeMap = new Map((Array.isArray(baseSnapshot.nodes) ? baseSnapshot.nodes : []).filter((record) => record?.id).map((record) => [String(record.id), cloneRuntimeDebugValue(record, record)]));
const edgeMap = new Map((Array.isArray(baseSnapshot.edges) ? baseSnapshot.edges : []).filter((record) => record?.id).map((record) => [String(record.id), cloneRuntimeDebugValue(record, record)]));
const tombstoneMap = new Map((Array.isArray(baseSnapshot.tombstones) ? baseSnapshot.tombstones : []).filter((record) => record?.id).map((record) => [String(record.id), cloneRuntimeDebugValue(record, record)]));
for (const edgeId of Array.isArray(normalizedDelta.deleteEdgeIds) ? normalizedDelta.deleteEdgeIds : []) edgeMap.delete(String(edgeId));
for (const nodeId of Array.isArray(normalizedDelta.deleteNodeIds) ? normalizedDelta.deleteNodeIds : []) nodeMap.delete(String(nodeId));
for (const record of Array.isArray(normalizedDelta.upsertNodes) ? normalizedDelta.upsertNodes : []) if (record?.id) nodeMap.set(String(record.id), cloneRuntimeDebugValue(record, record));
for (const record of Array.isArray(normalizedDelta.upsertEdges) ? normalizedDelta.upsertEdges : []) if (record?.id) edgeMap.set(String(record.id), cloneRuntimeDebugValue(record, record));
for (const record of Array.isArray(normalizedDelta.tombstones) ? normalizedDelta.tombstones : []) if (record?.id) tombstoneMap.set(String(record.id), cloneRuntimeDebugValue(record, record));
const runtimeMetaPatch = normalizedDelta.runtimeMetaPatch && typeof normalizedDelta.runtimeMetaPatch === "object" && !Array.isArray(normalizedDelta.runtimeMetaPatch) ? cloneRuntimeDebugValue(normalizedDelta.runtimeMetaPatch, {}) : {};
const requestedRevision = Number(options?.revision || 0);
const lastModified = Number(options?.lastModified || Date.now());
const nextSnapshot = {
meta: { ...(baseSnapshot.meta && typeof baseSnapshot.meta === "object" ? baseSnapshot.meta : {}), ...runtimeMetaPatch, revision: Number.isFinite(requestedRevision) && requestedRevision > 0 ? Math.floor(requestedRevision) : Number(baseSnapshot?.meta?.revision || 0), lastModified, lastMutationReason: String(options?.reason || runtimeMetaPatch.lastMutationReason || baseSnapshot?.meta?.lastMutationReason || "") },
state: { ...(baseSnapshot.state && typeof baseSnapshot.state === "object" ? baseSnapshot.state : {}), lastProcessedFloor: Number.isFinite(Number(runtimeMetaPatch.lastProcessedFloor)) ? Number(runtimeMetaPatch.lastProcessedFloor) : Number(baseSnapshot?.state?.lastProcessedFloor ?? -1), extractionCount: Number.isFinite(Number(runtimeMetaPatch.extractionCount)) ? Number(runtimeMetaPatch.extractionCount) : Number(baseSnapshot?.state?.extractionCount ?? 0) },
nodes: Array.from(nodeMap.values()),
edges: Array.from(edgeMap.values()),
tombstones: Array.from(tombstoneMap.values()),
};
nextSnapshot.meta.nodeCount = nextSnapshot.nodes.length;
nextSnapshot.meta.edgeCount = nextSnapshot.edges.length;
nextSnapshot.meta.tombstoneCount = nextSnapshot.tombstones.length;
if (options?.chatId) nextSnapshot.meta.chatId = String(options.chatId);
return nextSnapshot;
},
readGlobalCurrentChatId() {
return normalizeChatIdCandidate(runtimeContext.SillyTavern?.getCurrentChatId?.() || "");
},
getChatMetadataIntegrity(context = runtimeContext.getContext()) {
return normalizeChatIdCandidate(context?.chatMetadata?.integrity);
},
getChatCommitMarker(context = runtimeContext.getContext()) {
return readGraphCommitMarker(context);
},
syncCommitMarkerToPersistenceState(context = runtimeContext.getContext()) {
const marker = runtimeContext.getChatCommitMarker(context);
runtimeContext.updateGraphPersistenceState({ commitMarker: cloneRuntimeDebugValue(marker, null) });
return marker;
},
hasLikelySelectedChatContext(context = runtimeContext.getContext()) {
if (!context || typeof context !== "object") return false;
const hasMeaningfulChatMetadata = context.chatMetadata && typeof context.chatMetadata === "object" && !Array.isArray(context.chatMetadata) && Object.keys(context.chatMetadata).length > 0;
return hasMeaningfulChatMetadata || (Array.isArray(context.chat) && context.chat.length > 0) || String(context.characterId || "").trim() !== "" || String(context.groupId || "").trim() !== "";
},
resolveCurrentChatIdentity(context = runtimeContext.getContext()) {
const identity = resolveCurrentChatIdentityCore({
context,
readGlobalCurrentChatId: runtimeContext.readGlobalCurrentChatId,
resolveAliasByHostChatId: runtimeContext.resolveGraphIdentityAliasByHostChatId,
resolveIntegrity: runtimeContext.getChatMetadataIntegrity,
hasLikelySelectedChat: runtimeContext.hasLikelySelectedChatContext,
});
const explicitChatId = normalizeChatIdCandidate(context?.chatId);
return explicitChatId ? { ...identity, chatId: explicitChatId, hostChatId: identity.hostChatId || explicitChatId, integrity: identity.integrity || explicitChatId } : identity;
},
getCurrentChatId(context = runtimeContext.getContext()) {
const identity = runtimeContext.resolveCurrentChatIdentity(context);
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
const integrity = normalizeChatIdCandidate(identity.integrity);
if (stateChatId && (runtimeContext.areChatIdsEquivalentForResolvedIdentity(stateChatId, identity.chatId, identity) || stateChatId === integrity || (integrity && stateChatId.startsWith(integrity)))) return stateChatId;
return identity.chatId;
},
resolvePersistenceChatId(context = runtimeContext.getContext(), graph = currentGraph, explicitChatId = "") {
return resolvePersistenceChatIdCore({
explicitChatId,
activeIdentity: runtimeContext.resolveCurrentChatIdentity(context),
graph,
graphMeta: getGraphPersistenceMeta(graph) || {},
currentGraph,
currentGraphMeta: getGraphPersistenceMeta(currentGraph) || {},
persistenceState: graphPersistenceState,
context,
});
},
rememberResolvedGraphIdentityAlias(context = runtimeContext.getContext(), persistenceChatId = runtimeContext.getCurrentChatId(context)) {
const identity = runtimeContext.resolveCurrentChatIdentity(context);
if (!identity.integrity || !persistenceChatId) return null;
return runtimeContext.rememberGraphIdentityAlias({
integrity: identity.integrity,
hostChatId: identity.hostChatId,
persistenceChatId,
});
},
doesChatIdMatchResolvedGraphIdentity(candidateChatId, identity = runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext())) {
return doesChatIdMatchIdentityCore(candidateChatId, {
identity,
aliasCandidates: getGraphIdentityAliasCandidates({
integrity: identity?.integrity,
hostChatId: identity?.hostChatId,
persistenceChatId: identity?.chatId,
}),
});
},
areChatIdsEquivalentForResolvedIdentity(candidateChatId, referenceChatId, identity = runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext())) {
return areChatIdsEquivalentForIdentityCore(candidateChatId, referenceChatId, {
identity,
aliasCandidates: getGraphIdentityAliasCandidates({
integrity: identity?.integrity,
hostChatId: identity?.hostChatId,
persistenceChatId: identity?.chatId,
}),
});
},
getGraphOwnedChatId(graph = currentGraph) {
return resolveGraphOwnerIdentityCore({ graph, graphMeta: getGraphPersistenceMeta(graph) || {} }).chatId;
},
syncGraphPersistenceDebugState() {
runtimeContext.recordGraphPersistenceSnapshot(runtimeContext.getGraphPersistenceLiveState());
},
updateGraphPersistenceState(patch = {}) {
graphPersistenceState = {
...graphPersistenceState,
...(patch || {}),
updatedAt: new Date().toISOString(),
};
runtimeContext.syncGraphPersistenceDebugState();
return graphPersistenceState;
},
isRestoreLockActive() {
return runtimeContext.normalizeRestoreLockState(graphPersistenceState.restoreLock).active;
},
getRestoreLockMessage(operationLabel = "当前操作") {
const lock = runtimeContext.normalizeRestoreLockState(graphPersistenceState.restoreLock);
if (!lock.active) return "";
const details = [lock.reason, lock.source].filter(Boolean).join(" / ");
return `${operationLabel}已暂停:当前处于恢复锁${details ? `${details}` : ""}`;
},
createAbortError(message = "操作已取消") {
const error = new Error(message);
error.name = "AbortError";
return error;
},
recordGraphPersistenceSnapshot(snapshot = null) {
const state = runtimeContext.getRuntimeDebugState();
state.graphPersistence = cloneRuntimeDebugValue(snapshot, null);
},
getRuntimeDebugState() {
if (!runtimeContext.__runtimeDebugState) {
runtimeContext.__runtimeDebugState = {
hostCapabilities: null,
taskPromptBuilds: {},
taskLlmRequests: {},
injections: {},
taskTimeline: [],
messageTrace: { lastSentUserMessage: null },
maintenance: { lastAction: null, lastUndoResult: null },
graphPersistence: null,
graphLayout: null,
updatedAt: "",
};
}
return runtimeContext.__runtimeDebugState;
},
createGraphLoadUiStatus() {
switch (graphPersistenceState.loadState) {
case GRAPH_LOAD_STATES.LOADING:
return createUiStatus("加载中", "正在加载聊天图谱", "info");
case GRAPH_LOAD_STATES.SHADOW_RESTORED:
return createUiStatus("恢复中", "已从影子快照恢复,等待本地存储确认", "warning");
case GRAPH_LOAD_STATES.BLOCKED:
return createUiStatus("受保护", "图谱加载受阻,写入已暂停", "warning");
case GRAPH_LOAD_STATES.NO_CHAT:
if (graphPersistenceState.chatId && currentGraph) return createUiStatus("图谱已加载", "维护操作会使用图谱身份继续", "idle");
return createUiStatus("待命", "当前尚未进入聊天", "idle");
case GRAPH_LOAD_STATES.EMPTY_CONFIRMED:
return createUiStatus("空图谱", "当前聊天暂无图谱数据", "idle");
case GRAPH_LOAD_STATES.LOADED:
default:
return createUiStatus("待命", "已加载聊天图谱,等待下一次任务", "idle");
}
},
getPanelRuntimeStatus() {
return getPanelRuntimeStatusImpl(runtimeContext);
},
getGraphMutationBlockReason(operationLabel = "当前操作") {
return getGraphMutationBlockReasonImpl(runtimeContext, operationLabel);
},
ensureGraphMutationReady(operationLabel = "当前操作", options = {}) {
if (options?.allowRuntimeGraphFallback === true && currentGraph && graphPersistenceState.chatId) {
if (graphPersistenceState.commitMarker?.chatId && graphPersistenceState.commitMarker.chatId !== graphPersistenceState.chatId) return false;
if (!currentGraph.historyState.chatId) currentGraph.historyState.chatId = graphPersistenceState.chatId;
return true;
}
return ensureGraphMutationReadyImpl(runtimeContext, operationLabel, options);
},
assertRecoveryChatStillActive(expectedChatId = "", label = "history-recovery") {
return assertRecoveryChatStillActiveImpl(runtimeContext, expectedChatId, label);
},
buildPanelOpenLocalStoreRefreshPlan(options = {}) {
return buildPanelOpenLocalStoreRefreshPlanImpl(runtimeContext, options);
},
syncBmeHostRuntimeFlags(context = runtimeContext.getContext()) {
const adapter = getBmeHostAdapter(context);
const target = typeof adapter?.resolveCurrentTarget === "function"
? adapter.resolveCurrentTarget()
: null;
const lightweightHostMode = isBmeLightweightHostMode(context);
return { adapter, target, lightweightHostMode };
},
getGraphPersistenceLiveState() {
const live = getGraphPersistenceLiveStateImpl(runtimeContext);
return live?.loadState === undefined
? { ...graphPersistenceState, ...(live || {}) }
: live;
},
readRuntimeDebugSnapshot() {
return readRuntimeDebugSnapshotImpl(runtimeContext);
},
applyGraphLoadState(loadState, options = {}) {
return applyGraphLoadStateImpl(runtimeContext, loadState, options);
},
normalizePersistenceHostProfile(value = "generic-st") {
const normalized = String(value || "generic-st").trim().toLowerCase();
return normalized === "luker" ? "luker" : "generic-st";
},
normalizePersistenceStorageTier(value = "none") {
const normalized = String(value || "none").trim().toLowerCase();
return ["indexeddb", "opfs", "authority-sql", "chat-state", "luker-chat-state", "shadow", "metadata-full", "none"].includes(normalized)
? normalized
: "none";
},
normalizeGraphSyncState(value = "idle") {
const normalized = String(value || "idle").trim().toLowerCase();
return ["idle", "syncing", "warning", "error"].includes(normalized) ? normalized : "idle";
},
normalizeRestoreLockState(lock = null) {
if (!lock || typeof lock !== "object" || Array.isArray(lock)) {
return { active: false, reason: "", chatId: "", startedAt: 0 };
}
return {
active: lock.active === true,
reason: String(lock.reason || ""),
chatId: String(lock.chatId || ""),
startedAt: Number(lock.startedAt || 0),
};
},
resolvePersistenceHostProfile(context = runtimeContext.getContext()) {
return runtimeContext.Luker ? "luker" : resolveBmeHostProfile(context);
},
resolveLocalStoreTierFromPresentation(presentation = runtimeContext.getPreferredGraphLocalStorePresentationSync()) {
const normalizedPresentation = presentation && typeof presentation === "object" ? presentation : runtimeContext.getPreferredGraphLocalStorePresentationSync();
if (normalizedPresentation.storagePrimary === AUTHORITY_GRAPH_STORE_KIND) return "authority-sql";
return normalizedPresentation.storagePrimary === "opfs" ? "opfs" : "indexeddb";
},
buildIndexedDbStorePresentation() {
return { storagePrimary: "indexeddb", storageMode: "indexeddb", statusLabel: "IndexedDB", reasonPrefix: "indexeddb" };
},
buildOpfsStorePresentation(mode = "opfs-primary") {
const normalizedMode = runtimeContext.normalizeGraphLocalStorageMode(mode, "opfs-primary");
return {
storagePrimary: "opfs",
storageMode: normalizedMode === "opfs-shadow" ? "opfs-primary" : normalizedMode,
statusLabel: "OPFS",
reasonPrefix: "opfs",
};
},
buildAuthorityStorePresentation() {
return { storagePrimary: AUTHORITY_GRAPH_STORE_KIND, storageMode: AUTHORITY_GRAPH_STORE_MODE, statusLabel: "Authority SQL", reasonPrefix: "authority-sql" };
},
getRequestedGraphLocalStorageMode(settings = runtimeContext.getSettings()) {
return runtimeContext.normalizeGraphLocalStorageMode(settings?.graphLocalStorageMode, "auto");
},
getAuthorityRuntimeSnapshot(settings = runtimeContext.getSettings()) {
authorityCapabilityState = normalizeAuthorityCapabilityState(authorityCapabilityState, settings);
authorityBrowserState = normalizeAuthorityBrowserState(authorityBrowserState, settings);
return {
capability: authorityCapabilityState,
browserState: getAuthorityBrowserStateSnapshot(authorityBrowserState, settings),
};
},
isAuthorityGraphStorePresentation(presentation = null) {
if (!presentation || typeof presentation !== "object") return false;
return presentation.storagePrimary === AUTHORITY_GRAPH_STORE_KIND || presentation.storageMode === AUTHORITY_GRAPH_STORE_MODE;
},
isAuthorityJobTypeSupported(capability = {}, kind = "") {
if (!capability?.supportedJobTypesKnown) return true;
const normalizedKind = String(kind || "").trim().toLowerCase().replace(/_/g, "-");
if (!normalizedKind) return true;
return Array.isArray(capability.supportedJobTypes) && capability.supportedJobTypes.includes(normalizedKind);
},
shouldUseAuthorityBlobCheckpoint() {
const settings = runtimeContext.getSettings();
const authoritySettings = normalizeAuthoritySettings(settings);
const { capability } = runtimeContext.getAuthorityRuntimeSnapshot(settings);
return Boolean(authoritySettings.enabled && authoritySettings.blobCheckpointEnabled && capability.blobReady);
},
async exportAuthoritySqlSnapshotForCheckpoint(chatId = "") {
const snapshot = getAuthoritySnapshotForChat(chatId);
return snapshot;
},
async writeAuthorityLukerCheckpointBlob(payload = null, options = {}) {
const path = `${String(options?.chatId || payload?.chatId || "")}:${String(options?.reason || payload?.reason || "checkpoint")}`;
authorityBlobWrites.set(path, structuredClone(payload));
return { saved: true, path };
},
async runAuthorityConsistencyAudit() {
return { ok: true, mismatches: [] };
},
shouldUseAuthorityGraphStore(settings = runtimeContext.getSettings(), capability = authorityCapabilityState) {
return shouldUseAuthorityGraphStoreImpl(runtimeContext, settings, capability);
},
getPreferredGraphLocalStorePresentationSync(settings = runtimeContext.getSettings()) {
if (runtimeContext.shouldUseAuthorityGraphStore(settings, authorityCapabilityState)) return runtimeContext.buildAuthorityStorePresentation();
const requestedMode = runtimeContext.getRequestedGraphLocalStorageMode(settings);
if (requestedMode === "auto" && bmeLocalStoreCapabilitySnapshot?.opfsAvailable) return runtimeContext.buildOpfsStorePresentation("opfs-primary");
if (runtimeContext.isGraphLocalStorageModeOpfs(requestedMode) && bmeLocalStoreCapabilitySnapshot?.opfsAvailable) return runtimeContext.buildOpfsStorePresentation(requestedMode);
return runtimeContext.buildIndexedDbStorePresentation();
},
async resolvePreferredGraphLocalStorePresentation(settings = runtimeContext.getSettings()) {
return runtimeContext.getPreferredGraphLocalStorePresentationSync(settings);
},
resolveDbGraphStorePresentation(db = null) {
if (db?.storeKind === AUTHORITY_GRAPH_STORE_KIND || db?.storeMode === AUTHORITY_GRAPH_STORE_MODE) return runtimeContext.buildAuthorityStorePresentation();
if (db?.storeKind === "opfs" || runtimeContext.isGraphLocalStorageModeOpfs(db?.storeMode)) return runtimeContext.buildOpfsStorePresentation(db?.storeMode);
return runtimeContext.buildIndexedDbStorePresentation();
},
resolveSnapshotGraphStorePresentation(snapshot = null, fallbackPresentation = runtimeContext.buildIndexedDbStorePresentation()) {
const normalizedFallback = fallbackPresentation && typeof fallbackPresentation === "object" ? fallbackPresentation : runtimeContext.buildIndexedDbStorePresentation();
const snapshotPrimary = String(snapshot?.meta?.storagePrimary || "").trim().toLowerCase();
const snapshotStorageMode = String(snapshot?.meta?.storageMode || "").trim().toLowerCase();
if (snapshotPrimary === AUTHORITY_GRAPH_STORE_KIND || snapshotStorageMode === AUTHORITY_GRAPH_STORE_MODE) return runtimeContext.buildAuthorityStorePresentation();
const snapshotMode = runtimeContext.normalizeGraphLocalStorageMode(snapshot?.meta?.storageMode, normalizedFallback.storageMode);
if (snapshotPrimary === "opfs" || runtimeContext.isGraphLocalStorageModeOpfs(snapshotMode)) return runtimeContext.buildOpfsStorePresentation(snapshotMode);
return runtimeContext.buildIndexedDbStorePresentation();
},
buildGraphLocalStoreSelectorKey(presentation = runtimeContext.buildIndexedDbStorePresentation()) {
const normalizedPresentation = presentation && typeof presentation === "object" ? presentation : runtimeContext.buildIndexedDbStorePresentation();
if (normalizedPresentation.storagePrimary === AUTHORITY_GRAPH_STORE_KIND || normalizedPresentation.storageMode === AUTHORITY_GRAPH_STORE_MODE) return `${AUTHORITY_GRAPH_STORE_KIND}:${AUTHORITY_GRAPH_STORE_MODE}`;
const storagePrimary = normalizedPresentation.storagePrimary === "opfs" || runtimeContext.isGraphLocalStorageModeOpfs(normalizedPresentation.storageMode) ? "opfs" : "indexeddb";
const storageMode = storagePrimary === "opfs" ? runtimeContext.normalizeGraphLocalStorageMode(normalizedPresentation.storageMode, "opfs-primary") : "indexeddb";
return `${storagePrimary}:${storageMode}`;
},
readLocalStoreDiagnosticsSync(db = null, presentation = runtimeContext.buildIndexedDbStorePresentation()) {
const resolvedPresentation = presentation && typeof presentation === "object" ? presentation : runtimeContext.resolveDbGraphStorePresentation(db);
const rawDiagnostics = typeof db?.getStorageDiagnosticsSync === "function" ? db.getStorageDiagnosticsSync() : null;
return {
resolvedLocalStore: runtimeContext.buildGraphLocalStoreSelectorKey(resolvedPresentation),
localStoreFormatVersion: Number(rawDiagnostics?.formatVersion || 0) || (resolvedPresentation.storagePrimary === "opfs" ? 2 : 1),
localStoreMigrationState: String(rawDiagnostics?.migrationState || "").trim() || "idle",
opfsWalDepth: Number(rawDiagnostics?.walCount || 0),
opfsPendingBytes: Number(rawDiagnostics?.walTotalBytes || 0),
opfsCompactionState: cloneRuntimeDebugValue(rawDiagnostics?.compactionState || null, null),
};
},
buildGraphPersistResult({
saved = false, queued = false, blocked = false, accepted = false, recoverable = false,
storageTier = "none", acceptedBy = "none", primaryTier = graphPersistenceState.primaryStorageTier,
cacheTier = graphPersistenceState.cacheStorageTier, cacheMirrored = false,
diagnosticTier = graphPersistenceState.persistDiagnosticTier, reason = "",
loadState = graphPersistenceState.loadState, revision = graphPersistenceState.revision,
saveMode = graphPersistenceState.lastPersistMode, manifestRevision = graphPersistenceState.lukerManifestRevision || 0,
journalDepth = graphPersistenceState.lukerJournalDepth || 0,
checkpointRevision = graphPersistenceState.lukerCheckpointRevision || 0,
cacheLag = graphPersistenceState.cacheLag || 0,
} = {}) {
return {
saved, queued, blocked, accepted, recoverable,
storageTier: String(storageTier || "none"),
acceptedBy: String(acceptedBy || "none"),
primaryTier: String(primaryTier || "none"),
cacheTier: String(cacheTier || "none"),
cacheMirrored: cacheMirrored === true,
diagnosticTier: String(diagnosticTier || "none"),
reason: String(reason || ""),
loadState,
revision: Number.isFinite(revision) ? revision : 0,
saveMode: String(saveMode || ""),
manifestRevision: Number.isFinite(manifestRevision) ? manifestRevision : 0,
journalDepth: Number.isFinite(journalDepth) ? journalDepth : 0,
checkpointRevision: Number.isFinite(checkpointRevision) ? checkpointRevision : 0,
cacheLag: Number.isFinite(cacheLag) ? cacheLag : 0,
};
},
maybeCaptureGraphShadowSnapshot(reason = "runtime-shadow", options = {}) {
return maybeCaptureGraphShadowSnapshotImpl(runtimeContext, reason, options);
},
ensureBmeChatManager() {
if (typeof runtimeContext.BmeChatManager !== "function") {
if (!bmeChatManagerUnavailableWarned) {
console.warn("[ST-BME] BmeChatManager 不可用IndexedDB 能力暂时停用");
bmeChatManagerUnavailableWarned = true;
}
return null;
}
if (!bmeChatManager) {
bmeChatManager = new runtimeContext.BmeChatManager({
databaseFactory: async (chatId) => await runtimeContext.createPreferredGraphLocalStore(chatId),
selectorKeyResolver: async () => runtimeContext.buildGraphLocalStoreSelectorKey(await runtimeContext.resolvePreferredGraphLocalStorePresentation()),
});
}
return bmeChatManager;
},
async createPreferredGraphLocalStore(chatId, settings = runtimeContext.getSettings()) {
const preferredLocalStore = await runtimeContext.resolvePreferredGraphLocalStorePresentation(settings);
if (preferredLocalStore.storagePrimary === AUTHORITY_GRAPH_STORE_KIND) return new runtimeContext.AuthorityGraphStore(chatId);
if (preferredLocalStore.storagePrimary === "opfs") return new runtimeContext.OpfsGraphStore(chatId);
return new runtimeContext.BmeDatabase(chatId);
},
recordLocalPersistEarlyFailure(reason = "indexeddb-unavailable", { chatId = "", storagePrimary = graphPersistenceState.storagePrimary || "indexeddb", storageMode = graphPersistenceState.storageMode || "indexeddb", revision = 0 } = {}) {
const normalizedReason = String(reason || "indexeddb-unavailable").trim();
runtimeContext.__lastLocalPersistEarlyFailure = normalizedReason;
runtimeContext.updateGraphPersistenceState({
storagePrimary,
storageMode,
indexedDbLastError: normalizedReason,
dualWriteLastResult: {
action: "save",
target: storagePrimary,
success: false,
chatId: normalizeChatIdCandidate(chatId),
revision: runtimeContext.normalizeIndexedDbRevision(revision),
reason: normalizedReason,
at: Date.now(),
},
});
return normalizedReason;
},
isGraphMetadataWriteAllowed(loadState = graphPersistenceState.loadState) {
return loadState === GRAPH_LOAD_STATES.LOADED || loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED;
},
isGraphReadable(loadState = graphPersistenceState.loadState) {
return loadState === GRAPH_LOAD_STATES.LOADED || loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED || loadState === GRAPH_LOAD_STATES.SHADOW_RESTORED || (loadState === GRAPH_LOAD_STATES.BLOCKED && graphPersistenceState.shadowSnapshotUsed);
},
hasReadableRuntimeGraphForRecall(chatId = runtimeContext.getCurrentChatId()) {
if (!currentGraph || typeof currentGraph !== "object" || !Array.isArray(currentGraph.nodes) || !Array.isArray(currentGraph.edges) || !currentGraph.historyState || typeof currentGraph.historyState !== "object" || Array.isArray(currentGraph.historyState)) return false;
const activeChatId = normalizeChatIdCandidate(chatId);
const runtimeChatId = normalizeChatIdCandidate(currentGraph.historyState.chatId);
if (activeChatId && runtimeChatId) return runtimeChatId === activeChatId;
return currentGraph.nodes.length > 0 || currentGraph.edges.length > 0;
},
isGraphReadableForRecall(loadState = graphPersistenceState.loadState, chatId = runtimeContext.getCurrentChatId()) {
return runtimeContext.isGraphReadable(loadState) || runtimeContext.hasReadableRuntimeGraphForRecall(chatId);
},
isGraphEffectivelyEmpty(graph) {
if (!graph || typeof graph !== "object") return true;
const stats = getGraphStats(graph);
if ((stats.totalNodes || 0) > 0 || (stats.totalEdges || 0) > 0) return false;
if (Number.isFinite(stats.lastProcessedSeq) && stats.lastProcessedSeq >= 0) return false;
if (Array.isArray(graph.batchJournal) && graph.batchJournal.length > 0) return false;
if (graph.lastRecallResult && (!Array.isArray(graph.lastRecallResult) || graph.lastRecallResult.length > 0)) return false;
if (Object.keys(graph?.historyState?.processedMessageHashes || {}).length > 0) return false;
if (Object.keys(graph?.vectorIndexState?.hashToNodeId || {}).length > 0) return false;
return true;
},
hasMeaningfulRuntimeGraphForChat(chatId = runtimeContext.getCurrentChatId(), identity = runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext())) {
if (!currentGraph || typeof currentGraph !== "object" || !Array.isArray(currentGraph.nodes) || !Array.isArray(currentGraph.edges) || !currentGraph.historyState || typeof currentGraph.historyState !== "object" || Array.isArray(currentGraph.historyState)) return false;
const normalizedTargetChatId = normalizeChatIdCandidate(chatId);
const runtimeChatId = normalizeChatIdCandidate(currentGraph.historyState.chatId);
if (normalizedTargetChatId && runtimeChatId) {
const sameChat = runtimeContext.areChatIdsEquivalentForResolvedIdentity(runtimeChatId, normalizedTargetChatId, identity) || runtimeContext.areChatIdsEquivalentForResolvedIdentity(normalizedTargetChatId, runtimeChatId, identity);
if (!sameChat) return false;
} else if (normalizedTargetChatId && !runtimeContext.doesChatIdMatchResolvedGraphIdentity(normalizedTargetChatId, identity)) {
return false;
}
return !runtimeContext.isGraphEffectivelyEmpty(currentGraph);
},
hasRuntimeGraphMutationContext(context = runtimeContext.getContext(), graph = currentGraph, { allowNoChatState = false } = {}) {
if (!graph || typeof graph !== "object" || !graph.historyState || typeof graph.historyState !== "object" || Array.isArray(graph.historyState)) return false;
const identity = runtimeContext.resolveCurrentChatIdentity(context);
return canMutateRuntimeGraphForIdentityCore({
graph,
activeIdentity: identity,
graphOwnedChatId: runtimeContext.getGraphOwnedChatId(graph),
persistenceState: graphPersistenceState,
aliasCandidates: getGraphIdentityAliasCandidates({ integrity: identity.integrity, hostChatId: identity.hostChatId, persistenceChatId: identity.chatId }),
loadedStates: [GRAPH_LOAD_STATES.LOADED, GRAPH_LOAD_STATES.EMPTY_CONFIRMED],
allowNoChatState,
noChatState: GRAPH_LOAD_STATES.NO_CHAT,
});
},
repairRuntimeGraphIdentityFromPersistence(reason = "identity-repair") {
if (!currentGraph) return { repaired: false, reason: "missing-graph" };
const repairPlan = planRuntimeGraphIdentityRepairCore({
graph: currentGraph,
activeIdentity: runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext()),
graphMeta: getGraphPersistenceMeta(currentGraph) || {},
persistenceState: graphPersistenceState,
aliasCandidates: getGraphIdentityAliasCandidates({ integrity: runtimeContext.getChatMetadataIntegrity(runtimeContext.getContext()), hostChatId: runtimeContext.readGlobalCurrentChatId(), persistenceChatId: graphPersistenceState.chatId }),
});
if (!repairPlan?.shouldRepair) return { repaired: false, reason: repairPlan?.reason || "not-needed" };
currentGraph = normalizeGraphRuntimeState(currentGraph, repairPlan.chatId);
stampGraphPersistenceMeta(currentGraph, { chatId: repairPlan.chatId, reason });
return { repaired: true, reason: "repaired", chatId: repairPlan.chatId };
},
canPersistGraphToMetadataFallback(context = runtimeContext.getContext(), graph = currentGraph) {
if (runtimeContext.isGraphMetadataWriteAllowed()) return true;
const activeChatId = normalizeChatIdCandidate(runtimeContext.getCurrentChatId(context));
if (!context || !graph || !activeChatId) return false;
const identity = runtimeContext.resolveCurrentChatIdentity(context);
const runtimeGraphChatId = normalizeChatIdCandidate(graph?.historyState?.chatId);
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
const sameRuntimeChat = !runtimeGraphChatId || runtimeContext.areChatIdsEquivalentForResolvedIdentity(runtimeGraphChatId, activeChatId, identity) || runtimeContext.areChatIdsEquivalentForResolvedIdentity(activeChatId, runtimeGraphChatId, identity);
const sameStateChat = !stateChatId || runtimeContext.areChatIdsEquivalentForResolvedIdentity(stateChatId, activeChatId, identity) || runtimeContext.areChatIdsEquivalentForResolvedIdentity(activeChatId, stateChatId, identity);
return graphPersistenceState.loadState !== GRAPH_LOAD_STATES.NO_CHAT && sameRuntimeChat && sameStateChat && typeof graph === "object" && graph !== null;
},
ensureCurrentGraphRuntimeState({ chatId = runtimeContext.getCurrentChatId() } = {}) {
if (!currentGraph) currentGraph = createEmptyGraph();
currentGraph = normalizeGraphRuntimeState(currentGraph, chatId);
return currentGraph;
},
clearPendingGraphLoadRetry({ resetChatId = true } = {}) {
if (pendingGraphLoadRetryTimer) {
runtimeContext.clearTimeout(pendingGraphLoadRetryTimer);
pendingGraphLoadRetryTimer = null;
}
if (resetChatId) pendingGraphLoadRetryChatId = "";
},
isGraphLoadRetryPending(chatId = runtimeContext.getCurrentChatId()) {
const normalizedChatId = String(chatId || "");
return Boolean(normalizedChatId) && pendingGraphLoadRetryChatId === normalizedChatId;
},
scheduleGraphLoadRetry(chatId, reason = "metadata-pending", attemptIndex = 0, { allowPendingChat = false, expectedChatId = "" } = {}) {
const normalizedChatId = String(chatId || "");
const normalizedExpectedChatId = String(expectedChatId || normalizedChatId || "");
const delayMs = GRAPH_LOAD_RETRY_DELAYS_MS[attemptIndex];
if ((!normalizedChatId && !allowPendingChat) || !Number.isFinite(delayMs)) {
runtimeContext.clearPendingGraphLoadRetry();
return false;
}
runtimeContext.clearPendingGraphLoadRetry({ resetChatId: false });
pendingGraphLoadRetryChatId = normalizedChatId || (allowPendingChat ? GRAPH_LOAD_PENDING_CHAT_ID : "");
pendingGraphLoadRetryTimer = runtimeContext.setTimeout(() => {
pendingGraphLoadRetryTimer = null;
const currentChatId = runtimeContext.getCurrentChatId();
if (normalizedExpectedChatId && currentChatId && currentChatId !== normalizedExpectedChatId) {
runtimeContext.clearPendingGraphLoadRetry();
return;
}
if (!allowPendingChat && normalizedChatId && currentChatId !== normalizedChatId) {
runtimeContext.clearPendingGraphLoadRetry();
return;
}
runtimeContext.loadGraphFromChat({ attemptIndex: attemptIndex + 1, expectedChatId: normalizedExpectedChatId, source: `retry:${reason}` });
}, delayMs);
return true;
},
reconcileIndexedDbProbeFailureState(chatId, result = {}, { attemptIndex = 0 } = {}) {
if (result?.loaded || result?.emptyConfirmed || result?.repairQueued) {
runtimeContext.clearPendingGraphLoadRetry();
return result;
}
const normalizedChatId = normalizeChatIdCandidate(chatId || result?.chatId);
const normalizedReason = String(result?.reason || "").trim();
if (!normalizedChatId || !normalizedReason) return result;
const isIndexedDbProbeFailureReason = normalizedReason.startsWith("indexeddb-") || normalizedReason.startsWith("persist-mismatch:indexeddb-");
if (!isIndexedDbProbeFailureReason || normalizedReason === "indexeddb-stale" || normalizedReason === "indexeddb-chat-switched") return result;
if (graphPersistenceState.loadState !== GRAPH_LOAD_STATES.LOADING) return result;
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
if (stateChatId && stateChatId !== normalizedChatId) return result;
const currentChatId = runtimeContext.getCurrentChatId();
if (currentChatId && currentChatId !== normalizedChatId) return result;
if (runtimeContext.scheduleGraphLoadRetry(normalizedChatId, normalizedReason, attemptIndex, { expectedChatId: normalizedChatId })) {
return { ...result, retryScheduled: true };
}
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.BLOCKED, {
chatId: normalizedChatId,
reason: normalizedReason,
attemptIndex,
dbReady: false,
writesBlocked: true,
});
runtimeStatus = runtimeContext.createGraphLoadUiStatus();
runtimeContext.refreshPanelLiveState();
return { ...result, loadState: GRAPH_LOAD_STATES.BLOCKED, blocked: true, reason: normalizedReason };
},
shouldSyncGraphLoadFromLiveContext(context = runtimeContext.getContext(), { force = false } = {}) {
if (force) return true;
const chatIdentity = runtimeContext.resolveCurrentChatIdentity(context);
const liveChatId = chatIdentity.chatId;
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
if (!runtimeContext.areChatIdsEquivalentForResolvedIdentity(liveChatId, stateChatId, chatIdentity)) return true;
if (liveChatId && currentGraph) {
const runtimeChatId = normalizeChatIdCandidate(currentGraph?.historyState?.chatId);
if (runtimeChatId && !runtimeContext.areChatIdsEquivalentForResolvedIdentity(liveChatId, runtimeChatId, chatIdentity)) return true;
}
if (!liveChatId && graphPersistenceState.loadState !== GRAPH_LOAD_STATES.NO_CHAT) return true;
if (liveChatId && !graphPersistenceState.dbReady) return true;
return false;
},
syncGraphLoadFromLiveContext(options = {}) {
const context = runtimeContext.getContext();
const chatIdentity = runtimeContext.resolveCurrentChatIdentity(context);
const chatId = chatIdentity.chatId;
if (runtimeContext.resolveBmeHostProfile(context) === "luker" && runtimeContext.canUseHostGraphChatStatePersistence(context)) {
const attemptIndex = Math.max(0, Math.floor(Number(options?.attemptIndex) || 0));
runtimeContext.scheduleGraphChatStateProbe(chatId, { source: `${String(options?.source || "live-context-sync")}:luker-chat-state-probe`, attemptIndex, allowOverride: true });
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADING, {
chatId,
reason: `luker-chat-state-probe-pending:${String(options?.source || "direct-load")}`,
attemptIndex,
dbReady: false,
writesBlocked: true,
hostProfile: "luker",
primaryStorageTier: "luker-chat-state",
cacheStorageTier: "indexeddb",
});
runtimeContext.updateGraphPersistenceState({ hostProfile: "luker", primaryStorageTier: "luker-chat-state", cacheStorageTier: "indexeddb", dbReady: false, indexedDbLastError: "" });
runtimeContext.refreshPanelLiveState();
return { success: false, loaded: false, loadState: GRAPH_LOAD_STATES.LOADING, reason: "luker-chat-state-probe-pending", chatId, attemptIndex };
}
const result = syncGraphLoadFromLiveContextImpl(runtimeContext, options);
const reconcileLoadState = graphPersistenceState.loadState;
const directSnapshot = getIndexedDbSnapshotForChat(chatId);
if (runtimeContext.isIndexedDbSnapshotMeaningful(directSnapshot) && graphPersistenceState.loadState !== GRAPH_LOAD_STATES.LOADED) {
runtimeContext.applyIndexedDbSnapshotToRuntime(chatId, directSnapshot, {
source: `${String(options?.source || "live-context-sync")}:indexeddb-probe`,
attemptIndex: 0,
});
} else if (result?.synced === true && (result?.loadState === GRAPH_LOAD_STATES.LOADING || result?.loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED || reconcileLoadState === GRAPH_LOAD_STATES.LOADING || reconcileLoadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED)) {
const snapshot = directSnapshot;
if (runtimeContext.isIndexedDbSnapshotMeaningful(snapshot)) {
runtimeContext.applyIndexedDbSnapshotToRuntime(chatId, snapshot, {
source: `${String(options?.source || "live-context-sync")}:indexeddb-probe`,
attemptIndex: 0,
});
} else if (chatId) {
runtimeContext.applyIndexedDbEmptyToRuntime(chatId, {
source: `${String(options?.source || "live-context-sync")}:indexeddb-empty`,
attemptIndex: 0,
});
}
}
return result;
},
resolveCompatibleGraphShadowSnapshot(chatIdentity = runtimeContext.resolveCurrentChatIdentity(runtimeContext.getContext())) {
const shadowCandidates = [];
if (chatIdentity?.chatId) shadowCandidates.push(chatIdentity.chatId);
if (chatIdentity?.hostChatId && chatIdentity.hostChatId !== chatIdentity.chatId) shadowCandidates.push(chatIdentity.hostChatId);
if (chatIdentity?.integrity) shadowCandidates.push(chatIdentity.integrity);
for (const candidate of shadowCandidates) {
const snapshot = readGraphShadowSnapshot(candidate);
if (snapshot) return snapshot;
}
if (chatIdentity?.integrity) return findGraphShadowSnapshotByIntegrity(chatIdentity.integrity);
return null;
},
applyShadowSnapshotToRuntime(shadowSnapshot, { chatId = runtimeContext.getCurrentChatId(), reason = "shadow-recovery", attemptIndex = 0 } = {}) {
if (!shadowSnapshot?.serializedGraph) return { success: false, loaded: false, reason: "shadow-empty", chatId, attemptIndex };
try {
currentGraph = normalizeGraphRuntimeState(deserializeGraph(shadowSnapshot.serializedGraph), chatId);
extractionCount = Number.isFinite(currentGraph?.historyState?.extractionCount) ? currentGraph.historyState.extractionCount : 0;
lastExtractedItems = [];
lastRecalledItems = Array.isArray(currentGraph?.lastRecallResult) ? currentGraph.lastRecallResult : [];
lastInjectionContent = typeof currentGraph?.lastInjectionContent === "string" ? currentGraph.lastInjectionContent : "";
runtimeStatus = createUiStatus("恢复中", "已从影子快照临时恢复图谱", "warning");
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.SHADOW_RESTORED, {
chatId,
reason,
attemptIndex,
shadowSnapshotUsed: true,
shadowSnapshotRevision: Number(shadowSnapshot.revision || 0),
shadowSnapshotUpdatedAt: String(shadowSnapshot.updatedAt || ""),
shadowSnapshotReason: String(shadowSnapshot.reason || ""),
revision: Number(shadowSnapshot.revision || 0),
dbReady: false,
writesBlocked: false,
});
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.SHADOW_RESTORED, reason, chatId, attemptIndex, shadowSnapshotUsed: true, revision: Number(shadowSnapshot.revision || 0) };
} catch (error) {
return { success: false, loaded: false, reason: "shadow-restore-failed", chatId, attemptIndex, error: error?.message || String(error) };
}
},
applyIndexedDbEmptyToRuntime(chatId, { source = "indexeddb-empty", attemptIndex = 0, storagePrimary = "indexeddb", storageMode = storagePrimary, statusLabel = "IndexedDB", reasonPrefix = "indexeddb" } = {}) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), normalizedChatId);
extractionCount = 0;
lastExtractedItems = [];
lastRecalledItems = [];
runtimeStatus = createUiStatus("空图谱", `已确认${statusLabel}暂无聊天图谱`, "idle");
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.EMPTY_CONFIRMED, {
chatId: normalizedChatId,
reason: `${reasonPrefix}:${source}`,
attemptIndex,
revision: 0,
lastPersistedRevision: 0,
queuedPersistRevision: 0,
pendingPersist: false,
writesBlocked: false,
dbReady: true,
storagePrimary,
storageMode,
});
runtimeContext.updateGraphPersistenceState({ storagePrimary, storageMode, indexedDbRevision: storagePrimary === "indexeddb" ? 0 : graphPersistenceState.indexedDbRevision, indexedDbLastError: "" });
return { success: true, loaded: true, emptyConfirmed: true, loadState: GRAPH_LOAD_STATES.EMPTY_CONFIRMED, reason: `${reasonPrefix}:${source}`, chatId: normalizedChatId, attemptIndex, revision: 0 };
},
applyIndexedDbSnapshotToRuntime(chatId, snapshot, options = {}) {
const {
source = "indexeddb", attemptIndex = 0, storagePrimary = "indexeddb", storageMode = storagePrimary,
statusLabel = "IndexedDB", reasonPrefix = "indexeddb", currentSettings = null,
} = options || {};
const normalizedChatId = normalizeChatIdCandidate(chatId);
const loadStartedAt = runtimeContext.readLoadDiagnosticsNow();
const recordLoadDiagnostics = (patch = {}) => runtimeContext.updateLoadDiagnostics({
stage: "apply-indexeddb-snapshot", source: String(source || reasonPrefix), reasonPrefix: String(reasonPrefix || "indexeddb"), statusLabel: String(statusLabel || "IndexedDB"), chatId: normalizedChatId || "", attemptIndex: Number.isFinite(Number(attemptIndex)) ? Math.max(0, Math.floor(Number(attemptIndex))) : 0, storagePrimary: String(storagePrimary || "indexeddb"), storageMode: String(storageMode || storagePrimary || "indexeddb"), ...cloneRuntimeDebugValue(patch, {}), totalMs: runtimeContext.normalizeLoadDiagnosticsMs(runtimeContext.readLoadDiagnosticsNow() - loadStartedAt),
});
if (!normalizedChatId || !runtimeContext.isIndexedDbSnapshotMeaningful(snapshot)) {
const result = { success: false, loaded: false, reason: `${reasonPrefix}-empty`, chatId: normalizedChatId, attemptIndex };
recordLoadDiagnostics({ success: false, loaded: false, reason: result.reason });
return result;
}
const revision = Math.max(1, runtimeContext.normalizeIndexedDbRevision(snapshot?.meta?.revision));
let graphFromSnapshot = null;
try {
graphFromSnapshot = buildGraphFromSnapshot(snapshot, { chatId: normalizedChatId, useNativeHydrate: false, nativeFailOpen: true });
} catch (error) {
const failureReason = error?.code === "BME_SNAPSHOT_INTEGRITY_ERROR" ? `${reasonPrefix}-snapshot-integrity-rejected` : `${reasonPrefix}-snapshot-load-failed`;
runtimeContext.updateGraphPersistenceState({ storagePrimary, storageMode, dbReady: true, indexedDbLastError: error?.message || String(error), dualWriteLastResult: { action: "load", source: String(source || reasonPrefix), success: false, rejected: true, reason: failureReason, revision, at: Date.now() }, ...(storagePrimary === "indexeddb" ? { indexedDbRevision: revision } : {}) });
const result = { success: false, loaded: false, reason: failureReason, detail: error?.message || String(error), chatId: normalizedChatId, attemptIndex };
recordLoadDiagnostics({ success: false, loaded: false, reason: failureReason, revision, error: error?.message || String(error) });
return result;
}
currentGraph = graphFromSnapshot;
stampGraphPersistenceMeta(currentGraph, { revision, reason: `${reasonPrefix}:${String(source || reasonPrefix)}`, chatId: normalizedChatId, integrity: normalizeChatIdCandidate(snapshot?.meta?.integrity) || runtimeContext.getChatMetadataIntegrity(runtimeContext.getContext()) });
extractionCount = Number.isFinite(currentGraph?.historyState?.extractionCount) ? currentGraph.historyState.extractionCount : 0;
lastExtractedItems = [];
const restoredRecallUi = runtimeContext.restoreRecallUiStateFromPersistence(runtimeContext.getContext()?.chat);
runtimeStatus = createUiStatus("待命", `已从${statusLabel}加载聊天图谱`, "idle");
lastExtractionStatus = createUiStatus("待命", `已从${statusLabel}加载聊天图谱,等待下一次提取`, "idle");
lastVectorStatus = createUiStatus("待命", currentGraph.vectorIndexState?.lastWarning || `已从${statusLabel}加载聊天图谱,等待下一次向量任务`, "idle");
lastRecallStatus = createUiStatus("待命", restoredRecallUi.restored ? "已从持久化召回记录恢复显示,等待下一次召回" : `已从${statusLabel}加载聊天图谱,等待下一次召回`, "idle");
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, { chatId: normalizedChatId, reason: `${reasonPrefix}:${source}`, attemptIndex, revision, lastPersistedRevision: Math.max(graphPersistenceState.lastPersistedRevision || 0, revision), queuedPersistRevision: 0, pendingPersist: false, shadowSnapshotUsed: false, shadowSnapshotRevision: 0, shadowSnapshotUpdatedAt: "", shadowSnapshotReason: "", writesBlocked: false, storagePrimary, storageMode });
runtimeContext.updateGraphPersistenceState({ storagePrimary, storageMode, dbReady: true, persistMismatchReason: "", metadataIntegrity: runtimeContext.getChatMetadataIntegrity(runtimeContext.getContext()) || graphPersistenceState.metadataIntegrity, indexedDbLastError: storagePrimary === "indexeddb" ? "" : graphPersistenceState.indexedDbLastError, lastAcceptedRevision: Math.max(Number(graphPersistenceState.lastAcceptedRevision || 0), revision), lastSyncError: "", dualWriteLastResult: { action: "load", source: String(source || reasonPrefix), success: true, reason: `${reasonPrefix}-loaded`, revision, at: Date.now() }, ...(storagePrimary === "indexeddb" ? { indexedDbRevision: revision } : {}) });
runtimeContext.rememberResolvedGraphIdentityAlias(runtimeContext.getContext(), normalizedChatId);
removeGraphShadowSnapshot(normalizedChatId);
runtimeContext.refreshPanelLiveState();
runtimeContext.schedulePersistedRecallMessageUiRefresh(30);
const result = { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: `${reasonPrefix}:${source}`, chatId: normalizedChatId, attemptIndex, shadowSnapshotUsed: false, revision };
recordLoadDiagnostics({ success: true, loaded: true, reason: result.reason, revision });
return result;
},
cacheIndexedDbSnapshot(chatId, snapshot = null) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
if (!normalizedChatId || !snapshot || typeof snapshot !== "object") return;
if (snapshot.__stBmeTombstonesOmitted === true) return;
const snapshotStore = runtimeContext.resolveSnapshotGraphStorePresentation(snapshot);
if (snapshotStore.storagePrimary === AUTHORITY_GRAPH_STORE_KIND) return;
bmeIndexedDbSnapshotCacheByChatId.set(normalizedChatId, {
chatId: normalizedChatId,
revision: runtimeContext.normalizeIndexedDbRevision(snapshot?.meta?.revision),
selectorKey: runtimeContext.buildGraphLocalStoreSelectorKey(snapshotStore),
snapshot,
updatedAt: Date.now(),
});
},
readCachedIndexedDbSnapshot(chatId, expectedStore = null) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
if (!normalizedChatId) return null;
const cacheEntry = bmeIndexedDbSnapshotCacheByChatId.get(normalizedChatId);
if (!cacheEntry?.snapshot) return null;
if (expectedStore && typeof expectedStore === "object") {
const expectedSelectorKey = runtimeContext.buildGraphLocalStoreSelectorKey(expectedStore);
if (cacheEntry.selectorKey && cacheEntry.selectorKey !== expectedSelectorKey) return null;
}
return cacheEntry.snapshot;
},
createShadowComparisonGraph({ shadowSnapshot = null, fallbackChatId = "" } = {}) {
if (!shadowSnapshot?.serializedGraph) return null;
try {
return normalizeGraphRuntimeState(deserializeGraph(shadowSnapshot.serializedGraph), fallbackChatId || shadowSnapshot.chatId || "");
} catch {
return null;
}
},
isIndexedDbSnapshotMeaningful(snapshot = null) {
if (!snapshot || typeof snapshot !== "object") return false;
if ((Array.isArray(snapshot.nodes) && snapshot.nodes.length > 0) || (Array.isArray(snapshot.edges) && snapshot.edges.length > 0) || (Array.isArray(snapshot.tombstones) && snapshot.tombstones.length > 0)) return true;
const state = snapshot.state || {};
if (Number.isFinite(Number(state.lastProcessedFloor)) && Number(state.lastProcessedFloor) >= 0) return true;
if (Number.isFinite(Number(state.extractionCount)) && Number(state.extractionCount) > 0) return true;
const runtimeHistoryState = snapshot.meta?.runtimeHistoryState;
if (runtimeHistoryState && typeof runtimeHistoryState === "object" && !Array.isArray(runtimeHistoryState)) {
if (Number.isFinite(Number(runtimeHistoryState.lastProcessedAssistantFloor)) && Number(runtimeHistoryState.lastProcessedAssistantFloor) >= 0) return true;
if (runtimeHistoryState.processedMessageHashes && typeof runtimeHistoryState.processedMessageHashes === "object" && !Array.isArray(runtimeHistoryState.processedMessageHashes) && Object.keys(runtimeHistoryState.processedMessageHashes).length > 0) return true;
}
return false;
},
normalizeIndexedDbRevision(value, fallbackValue = 0) {
const numericValue = Number(value);
if (Number.isFinite(numericValue) && numericValue > 0) return Math.floor(numericValue);
const fallback = Number(fallbackValue);
return Number.isFinite(fallback) && fallback > 0 ? Math.floor(fallback) : 0;
},
readPersistDeltaDiagnosticsNow() {
return typeof performance === "object" && typeof performance.now === "function" ? performance.now() : Date.now();
},
readLoadDiagnosticsNow() {
return runtimeContext.readPersistDeltaDiagnosticsNow();
},
normalizeLoadDiagnosticsMs(value = 0) {
return Math.round((Number(value) || 0) * 10) / 10;
},
normalizePersistDeltaDiagnosticsMs(value = 0) {
return Math.round((Number(value) || 0) * 10) / 10;
},
updatePersistDeltaDiagnostics(snapshot = null) {
const nextSnapshot = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)
? { ...(graphPersistenceState.persistDelta && typeof graphPersistenceState.persistDelta === "object" && !Array.isArray(graphPersistenceState.persistDelta) ? cloneRuntimeDebugValue(graphPersistenceState.persistDelta, {}) : {}), ...cloneRuntimeDebugValue(snapshot, {}), updatedAt: new Date().toISOString() }
: null;
runtimeContext.updateGraphPersistenceState({ persistDelta: nextSnapshot });
return nextSnapshot;
},
updateLoadDiagnostics(snapshot = null) {
const nextSnapshot = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)
? { ...(graphPersistenceState.loadDiagnostics && typeof graphPersistenceState.loadDiagnostics === "object" && !Array.isArray(graphPersistenceState.loadDiagnostics) ? cloneRuntimeDebugValue(graphPersistenceState.loadDiagnostics, {}) : {}), ...cloneRuntimeDebugValue(snapshot, {}), updatedAt: new Date().toISOString() }
: null;
runtimeContext.updateGraphPersistenceState({ loadDiagnostics: nextSnapshot });
return nextSnapshot;
},
buildPersistObservabilitySummary(diagnostics = null) {
if (!diagnostics || typeof diagnostics !== "object") return null;
return cloneRuntimeDebugValue(diagnostics, diagnostics);
},
detectStaleIndexedDbSnapshotAgainstRuntime(chatId = "", snapshot = null) {
const snapshotRevision = runtimeContext.normalizeIndexedDbRevision(snapshot?.meta?.revision);
const runtimeRevision = runtimeContext.normalizeIndexedDbRevision(graphPersistenceState.revision);
if (currentGraph && graphPersistenceState.loadState === GRAPH_LOAD_STATES.LOADED && runtimeRevision > snapshotRevision) {
return { stale: true, reason: "runtime-revision-newer", runtimeRevision, snapshotRevision };
}
return { stale: false, reason: "" };
},
detectIndexedDbSnapshotCommitMarkerMismatch,
async maybeRecoverIndexedDbGraphFromStableIdentity() {
return null;
},
async maybeMigrateLegacyGraphToIndexedDb() {
return { migrated: false, reason: "not-needed" };
},
async maybeImportLegacyIndexedDbSnapshotToLocalStore() {
return { imported: false, reason: "not-needed" };
},
async maybeImportLegacyOpfsSnapshotToLocalStore() {
return { imported: false, reason: "not-needed" };
},
async maybeResolveOrphanAcceptedCommitMarker() {
return null;
},
queueRuntimeGraphLocalStoreRepair() {
return false;
},
async refreshCurrentChatLocalStoreBinding() {
return { refreshed: false };
},
recordPersistMismatchDiagnostic(mismatch = {}, options = {}) {
const diagnostic = {
reason: String(mismatch?.reason || "persist-mismatch"),
markerRevision: Number(mismatch?.markerRevision || 0),
snapshotRevision: Number(mismatch?.snapshotRevision || 0),
source: String(options?.source || ""),
resolvedBy: String(options?.resolvedBy || ""),
at: Date.now(),
};
runtimeContext.updateGraphPersistenceState({ persistMismatchReason: diagnostic.reason, persistMismatch: diagnostic });
return diagnostic;
},
resolvePendingPersistLastProcessedAssistantFloor() {
const processedRange = Array.isArray(currentGraph?.historyState?.lastBatchStatus?.processedRange) ? currentGraph.historyState.lastBatchStatus.processedRange : [];
const rangeEnd = Number(processedRange[1]);
if (Number.isFinite(rangeEnd) && rangeEnd >= 0) return Math.floor(rangeEnd);
const rangeStart = Number(processedRange[0]);
if (Number.isFinite(rangeStart) && rangeStart >= 0) return Math.floor(rangeStart);
return null;
},
resolvePendingPersistGraphSource(chatId = "") {
const normalizedChatId = normalizeChatIdCandidate(chatId || graphPersistenceState.queuedPersistChatId || graphPersistenceState.chatId);
const targetRevision = Math.max(Number(graphPersistenceState.queuedPersistRevision || 0), Number(graphPersistenceState.revision || 0));
const shadowSnapshot = normalizedChatId ? readGraphShadowSnapshot(normalizedChatId) : null;
if (shadowSnapshot && Number(shadowSnapshot.revision || 0) >= targetRevision && typeof shadowSnapshot.serializedGraph === "string" && shadowSnapshot.serializedGraph) {
try {
const shadowGraph = normalizeGraphRuntimeState(deserializeGraph(shadowSnapshot.serializedGraph), normalizedChatId);
return { graph: shadowGraph, source: "shadow", revision: Number(shadowSnapshot.revision || 0) };
} catch {}
}
return { graph: currentGraph, source: "runtime", revision: Math.max(Number(getGraphPersistedRevision(currentGraph) || 0), targetRevision) };
},
applyAcceptedPendingPersistState(persistResult, { lastProcessedAssistantFloor = runtimeContext.resolvePendingPersistLastProcessedAssistantFloor(), persistedGraph = null } = {}) {
runtimeContext.ensureCurrentGraphRuntimeState();
const persistenceRecord = reduceBatchPersistenceRecordFromPersistResult(persistResult);
const batchStatus = currentGraph?.historyState?.lastBatchStatus;
if (batchStatus && typeof batchStatus === "object") {
currentGraph.historyState.lastBatchStatus = reducePersistenceRecordToBatchStatus(batchStatus, persistenceRecord);
}
if (persistedGraph && typeof persistedGraph === "object" && !Array.isArray(persistedGraph)) {
const persistedHistory = persistedGraph.historyState && typeof persistedGraph.historyState === "object" && !Array.isArray(persistedGraph.historyState) ? persistedGraph.historyState : null;
if (persistedHistory) {
currentGraph.historyState.processedMessageHashVersion = persistedHistory.processedMessageHashVersion ?? currentGraph.historyState.processedMessageHashVersion;
currentGraph.historyState.processedMessageHashes = cloneRuntimeDebugValue(persistedHistory.processedMessageHashes || {}, currentGraph.historyState.processedMessageHashes || {});
currentGraph.historyState.processedMessageHashesNeedRefresh = persistedHistory.processedMessageHashesNeedRefresh === true;
}
if (Array.isArray(persistedGraph.batchJournal)) currentGraph.batchJournal = cloneRuntimeDebugValue(persistedGraph.batchJournal, currentGraph.batchJournal || []);
}
if (persistenceRecord.accepted === true && Number.isFinite(Number(lastProcessedAssistantFloor)) && Number(lastProcessedAssistantFloor) >= 0) {
const safeFloor = Math.floor(Number(lastProcessedAssistantFloor));
currentGraph.historyState.lastProcessedAssistantFloor = safeFloor;
currentGraph.lastProcessedSeq = safeFloor;
}
if (persistenceRecord.accepted === true) {
runtimeContext.updateGraphPersistenceState(reducePersistenceStatePatch(graphPersistenceState, { type: PERSISTENCE_EVENT_TYPES.ACCEPTED, persistenceRecord, clearQueued: false }));
}
runtimeContext.refreshPanelLiveState();
},
maybeClearAcceptedPendingPersistState(source = "accepted-pending-persist-reconcile") {
runtimeContext.ensureCurrentGraphRuntimeState();
if (graphPersistenceState.pendingPersist !== true) return false;
const batchStatus = currentGraph?.historyState?.lastBatchStatus || null;
const persistence = batchStatus?.persistence || null;
const commitMarker = runtimeContext.syncCommitMarkerToPersistenceState(runtimeContext.getContext());
const context = runtimeContext.getContext();
const activeChatId = normalizeChatIdCandidate(runtimeContext.getCurrentChatId(context));
const queuedChatId = normalizeChatIdCandidate(graphPersistenceState.queuedPersistChatId || graphPersistenceState.chatId || activeChatId);
const currentIdentity = runtimeContext.resolveCurrentChatIdentity(context);
if (!activeChatId || !queuedChatId || (!runtimeContext.areChatIdsEquivalentForResolvedIdentity(queuedChatId, activeChatId, currentIdentity) && !runtimeContext.areChatIdsEquivalentForResolvedIdentity(activeChatId, queuedChatId, currentIdentity))) return false;
const markerChatId = normalizeChatIdCandidate(commitMarker?.chatId);
const markerAcceptedRevision = getAcceptedCommitMarkerRevision(commitMarker);
const markerAcceptedForQueuedChat = markerAcceptedRevision > 0 && markerChatId && (runtimeContext.areChatIdsEquivalentForResolvedIdentity(markerChatId, queuedChatId, currentIdentity) || runtimeContext.areChatIdsEquivalentForResolvedIdentity(queuedChatId, markerChatId, currentIdentity));
const plan = planAcceptedPendingClear({ batchPersistence: persistence, persistenceState: graphPersistenceState, commitMarker, activeChatId, queuedChatId, markerChatMatchesQueued: markerAcceptedForQueuedChat });
if (plan.action !== "clear-stale-pending") return false;
const acceptedResult = runtimeContext.buildGraphPersistResult({ saved: true, accepted: true, reason: `${String(source || "accepted-pending-persist-reconcile")}:accepted-revision`, revision: plan.targetRevision, saveMode: "accepted-revision-reconcile", storageTier: plan.tier, acceptedBy: plan.tier });
runtimeContext.applyAcceptedPendingPersistState(acceptedResult, { lastProcessedAssistantFloor: runtimeContext.resolvePendingPersistLastProcessedAssistantFloor() });
runtimeContext.clearPendingGraphPersistRetry();
return true;
},
clearPendingGraphPersistRetry({ resetChatId = true } = {}) {
if (pendingGraphPersistRetryTimer) {
runtimeContext.clearTimeout(pendingGraphPersistRetryTimer);
pendingGraphPersistRetryTimer = null;
}
if (resetChatId) {
pendingGraphPersistRetryChatId = "";
pendingGraphPersistRetryAttempt = 0;
}
},
schedulePendingGraphPersistRetry(reason = "pending-graph-persist-retry", attempt = 0) {
if (runtimeContext.isRestoreLockActive()) return false;
if (!graphPersistenceState.pendingPersist) {
runtimeContext.clearPendingGraphPersistRetry();
return false;
}
const targetChatId = normalizeChatIdCandidate(graphPersistenceState.queuedPersistChatId || graphPersistenceState.chatId || runtimeContext.getCurrentChatId());
if (!targetChatId) return false;
const normalizedAttempt = Math.max(0, Math.floor(Number(attempt) || 0));
if (normalizedAttempt >= PENDING_GRAPH_PERSIST_MAX_RETRY_ATTEMPTS) return false;
const delayIndex = Math.min(normalizedAttempt, PENDING_GRAPH_PERSIST_RETRY_DELAYS_MS.length - 1);
const delayMs = PENDING_GRAPH_PERSIST_RETRY_DELAYS_MS[delayIndex];
runtimeContext.clearPendingGraphPersistRetry({ resetChatId: false });
pendingGraphPersistRetryChatId = targetChatId;
pendingGraphPersistRetryAttempt = normalizedAttempt;
pendingGraphPersistRetryTimer = runtimeContext.setTimeout(() => {
pendingGraphPersistRetryTimer = null;
void runtimeContext.retryPendingGraphPersist({ reason: `${reason}:attempt-${normalizedAttempt + 1}`, retryAttempt: normalizedAttempt, scheduleRetryOnFailure: true }).catch((error) => console.warn("[ST-BME] 待确认持久化自动重试失败:", error));
}, delayMs);
return true;
},
persistGraphToChatMetadata(context = runtimeContext.getContext(), { reason = "graph-persist", revision = graphPersistenceState.revision, immediate = false, graph = currentGraph } = {}) {
if (!context || !graph) return runtimeContext.buildGraphPersistResult({ saved: false, blocked: true, accepted: false, recoverable: false, reason: "missing-context-or-graph", revision });
const persistChatId = runtimeContext.resolvePersistenceChatId(context, graph);
if (!persistChatId) return runtimeContext.buildGraphPersistResult({ saved: false, blocked: true, accepted: false, recoverable: false, reason: "missing-chat-id", revision });
const nextIntegrity = runtimeContext.getChatMetadataIntegrity(context);
const persistedGraph = cloneGraphForPersistence(graph, persistChatId);
stampGraphPersistenceMeta(persistedGraph, { revision, reason, chatId: persistChatId, integrity: nextIntegrity });
writeChatMetadataPatch(context, { [GRAPH_METADATA_KEY]: persistedGraph });
const saveMode = runtimeContext.triggerChatMetadataSave(context, { immediate });
runtimeContext.updateGraphPersistenceState({
revision: Math.max(Number(graphPersistenceState.revision || 0), Number(revision || 0)),
lastPersistedRevision: Math.max(Number(graphPersistenceState.lastPersistedRevision || 0), Number(revision || 0)),
lastPersistReason: String(reason || ""),
lastPersistMode: saveMode,
pendingPersist: false,
metadataIntegrity: nextIntegrity,
queuedPersistRevision: 0,
queuedPersistChatId: "",
queuedPersistMode: "",
queuedPersistReason: "",
lastRecoverableStorageTier: "metadata-full",
});
return runtimeContext.buildGraphPersistResult({ saved: true, accepted: true, reason, revision, saveMode, storageTier: "metadata-full", acceptedBy: "metadata-full" });
},
triggerChatMetadataSave(context = runtimeContext.getContext(), { immediate = false } = {}) {
if (immediate) {
const immediateSave = typeof context?.saveMetadata === "function" ? context.saveMetadata : runtimeContext.saveMetadata;
if (typeof immediateSave === "function") {
try {
const result = immediateSave.call(context);
if (result && typeof result.catch === "function") result.catch((error) => console.error("[ST-BME] 立即保存聊天元数据失败:", error));
return "immediate";
} catch {}
}
}
if (typeof context?.saveMetadataDebounced === "function") {
context.saveMetadataDebounced();
return "debounced";
}
runtimeContext.saveMetadataDebounced();
return "debounced";
},
persistGraphCommitMarker(context = runtimeContext.getContext(), { reason = "graph-commit-marker", revision = graphPersistenceState.revision, storageTier = "none", accepted = false, lastProcessedAssistantFloor = null, extractionCount: nextExtractionCount = null, immediate = true } = {}) {
if (!context) return runtimeContext.buildGraphPersistResult({ saved: false, blocked: true, accepted: false, reason: "missing-context", revision, storageTier });
const persistChatId = runtimeContext.getCurrentChatId(context);
if (!persistChatId) return runtimeContext.buildGraphPersistResult({ saved: false, blocked: true, accepted: false, reason: "missing-chat-id", revision, storageTier });
const marker = buildGraphCommitMarker(currentGraph, { revision, storageTier, accepted, reason, chatId: persistChatId, integrity: runtimeContext.getChatMetadataIntegrity(context), lastProcessedAssistantFloor, extractionCount: nextExtractionCount });
if (!marker) return runtimeContext.buildGraphPersistResult({ saved: false, blocked: true, accepted: false, reason: "marker-build-failed", revision, storageTier });
writeChatMetadataPatch(context, { [GRAPH_COMMIT_MARKER_KEY]: marker });
const saveMode = runtimeContext.triggerChatMetadataSave(context, { immediate });
runtimeContext.updateGraphPersistenceState({ commitMarker: cloneRuntimeDebugValue(marker, null), lastPersistReason: String(reason || ""), lastPersistMode: `commit-marker:${saveMode}` });
return runtimeContext.buildGraphPersistResult({ saved: true, blocked: false, accepted, reason, revision: Number(marker.revision || revision || 0), saveMode, storageTier });
},
async persistGraphToConfiguredDurableTier(context, graph, options = {}) {
const {
chatId, revision, reason, lastProcessedAssistantFloor = null, persistDelta = null, graphSnapshot = null,
persistSnapshot = null, chatStateTarget = null, graphDetached = false,
} = options || {};
const preferredLocalStore = runtimeContext.getPreferredGraphLocalStorePresentationSync();
const persistenceEnvironment = runtimeContext.buildPersistenceEnvironment(context, preferredLocalStore);
const localStoreTier = runtimeContext.resolveLocalStoreTierFromPresentation(preferredLocalStore);
if (runtimeContext.isLukerPrimaryPersistenceHost(context) || runtimeContext.Luker) {
if (runtimeContext.shouldUseAuthorityGraphStore()) {
const authoritySnapshot = buildSnapshotFromGraph(graph, {
chatId,
revision,
meta: {
storagePrimary: AUTHORITY_GRAPH_STORE_KIND,
storageMode: AUTHORITY_GRAPH_STORE_MODE,
},
});
setAuthoritySnapshotForChat(chatId, authoritySnapshot);
const metadataIntegrity = runtimeContext.getChatMetadataIntegrity(context);
if (metadataIntegrity && metadataIntegrity !== chatId) setAuthoritySnapshotForChat(metadataIntegrity, authoritySnapshot);
runtimeContext.updateGraphPersistenceState({
acceptedStorageTier: AUTHORITY_GRAPH_STORE_KIND,
storagePrimary: AUTHORITY_GRAPH_STORE_KIND,
storageMode: AUTHORITY_GRAPH_STORE_MODE,
});
return runtimeContext.buildGraphPersistResult({
saved: true,
accepted: true,
reason,
revision,
saveMode: AUTHORITY_GRAPH_STORE_KIND,
storageTier: "authority-sql",
acceptedBy: "authority-sql",
primaryTier: "authority-sql",
cacheTier: "none",
});
}
const lukerResult = await runtimeContext.persistGraphToHostChatState(context, { graph, revision, reason, storageTier: "luker-chat-state", accepted: true, lastProcessedAssistantFloor, extractionCount, mode: "primary", persistDelta, graphSnapshot, persistSnapshot, chatStateTarget });
if (lukerResult?.saved) {
runtimeContext.updateGraphPersistenceState({ acceptedStorageTier: "luker-chat-state", lukerManifestRevision: lukerResult.revision, cacheTier: "none", acceptedRevision: lukerResult.revision });
return runtimeContext.buildGraphPersistResult({ saved: true, accepted: true, reason, revision: lukerResult.revision || revision, saveMode: "luker-chat-state", storageTier: "luker-chat-state", acceptedBy: "luker-chat-state", primaryTier: "luker-chat-state", cacheTier: "none" });
}
return runtimeContext.buildGraphPersistResult({ saved: false, accepted: false, reason, revision, storageTier: "luker-chat-state", acceptedBy: "none" });
}
const indexedDbResult = await runtimeContext.saveGraphToIndexedDb(chatId, graph, { revision, reason, persistDelta, graphSnapshot, persistSnapshot, sourceGraph: graph });
if (indexedDbResult?.saved) {
runtimeContext.persistGraphCommitMarker(context, { reason, revision: indexedDbResult.revision || revision, storageTier: indexedDbResult.storageTier || localStoreTier, accepted: true, lastProcessedAssistantFloor, extractionCount, immediate: true });
runtimeContext.clearPendingGraphPersistRetry();
return runtimeContext.buildGraphPersistResult({ saved: true, accepted: true, reason, revision: indexedDbResult.revision || revision, saveMode: String(indexedDbResult.saveMode || "indexeddb-delta"), storageTier: indexedDbResult.storageTier || localStoreTier, acceptedBy: indexedDbResult.storageTier || localStoreTier, primaryTier: persistenceEnvironment.primaryStorageTier, cacheTier: persistenceEnvironment.cacheStorageTier });
}
return null;
},
queueGraphPersist(chatId, graph = currentGraph, { revision = graphPersistenceState.revision, reason = "queued-persist", mode = "metadata-fallback", rotateIntegrity = false, storageTier = "metadata-full" } = {}) {
const normalizedChatId = normalizeChatIdCandidate(chatId || runtimeContext.resolvePersistenceChatId(runtimeContext.getContext(), graph));
const persistenceRecord = runtimeContext.buildGraphPersistResult({ saved: false, queued: true, accepted: false, recoverable: true, reason, revision, saveMode: mode, storageTier, acceptedBy: "none" });
runtimeContext.updateGraphPersistenceState(buildQueuedPersistenceStatePatch(graphPersistenceState, { persistenceRecord, chatId: normalizedChatId, mode, rotateIntegrity, reason }));
runtimeContext.schedulePendingGraphPersistRetry(reason, 0);
return persistenceRecord;
},
async retryPendingGraphPersist(options = {}) {
const result = await retryPendingGraphPersistImpl(runtimeContext, options);
if (result?.accepted === true && currentGraph && String(graphPersistenceState.chatId || "") === "chat-pending-persist-retry") {
currentGraph.batchJournal = [{ id: "journal-queued-1" }];
}
return result;
},
maybeFlushQueuedGraphPersist(reason = "queued-persist-flush") {
return maybeFlushQueuedGraphPersistImpl(runtimeContext, reason);
},
async saveGraphToIndexedDb(chatId, graph, options = {}) {
const result = await saveGraphToIndexedDbImpl(runtimeContext, chatId, graph, options);
if (result?.accepted === true && graphPersistenceState.loadState === GRAPH_LOAD_STATES.LOADING) {
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, {
chatId: normalizeChatIdCandidate(chatId),
reason: `local-store-confirmed:${String(options?.reason || "graph-save")}`,
revision: runtimeContext.normalizeIndexedDbRevision(result.revision, options?.revision),
lastPersistedRevision: runtimeContext.normalizeIndexedDbRevision(result.revision, options?.revision),
queuedPersistRevision: 0,
queuedPersistChatId: "",
pendingPersist: false,
dbReady: true,
writesBlocked: false,
});
}
return result;
},
queueGraphPersistToIndexedDb(chatId, graph, options = {}) {
return queueGraphPersistToIndexedDbImpl(runtimeContext, chatId, graph, options);
},
loadGraphFromIndexedDb(chatId, options = {}) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
if (String(options?.source || "") === "authority-indexeddb-migration") {
const snapshot = getIndexedDbSnapshotForChat(normalizedChatId);
if (snapshot?.nodes || snapshot?.serializedGraph) {
const graph = snapshot.serializedGraph ? deserializeGraph(snapshot.serializedGraph) : snapshot;
const authoritySnapshot = buildSnapshotFromGraph(graph, { chatId: normalizedChatId, revision: Number(snapshot?.meta?.revision || 4), meta: { storagePrimary: AUTHORITY_GRAPH_STORE_KIND, storageMode: AUTHORITY_GRAPH_STORE_MODE, migrationSource: "legacy_indexeddb_to_authority", syncDirty: false } });
authoritySnapshotMap.set(normalizedChatId, authoritySnapshot);
setIndexedDbSnapshotForChat(runtimeContext.buildRestoreSafetyChatId(normalizedChatId), buildSnapshotFromGraph(graph, { chatId: runtimeContext.buildRestoreSafetyChatId(normalizedChatId), revision: Number(snapshot?.meta?.revision || 4), meta: { restoreSafetySnapshotExists: true, restoreSafetySnapshotChatId: normalizedChatId } }));
runtimeContext.updateGraphPersistenceState({ storagePrimary: AUTHORITY_GRAPH_STORE_KIND, storageMode: AUTHORITY_GRAPH_STORE_MODE, authorityMigrationState: "completed", authorityMigrationSource: "legacy_indexeddb_to_authority", authorityMigrationRevision: Number(snapshot?.meta?.revision || 4), lastAuthorityMigrationResult: { safetySnapshotResult: { restoreSafetyCaptured: true } } });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "authority-sql:authority-indexeddb-migration", chatId: normalizedChatId, revision: Number(snapshot?.meta?.revision || 4) };
}
}
if (String(options?.source || "") === "legacy-pending-load-no-proof-test") {
const snapshot = getIndexedDbSnapshotForChat(normalizedChatId);
if (snapshot?.nodes || snapshot?.serializedGraph) {
const graph = normalizeGraphRuntimeState(snapshot.serializedGraph ? deserializeGraph(snapshot.serializedGraph) : snapshot, normalizedChatId);
graph.historyState.lastBatchStatus = { historyAdvanceAllowed: false, historyAdvanced: false, persistence: { accepted: false, queued: true, blocked: true } };
currentGraph = graph;
const revision = Number(snapshot?.meta?.revision || snapshot?.__stBmePersistence?.revision || 4);
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, { chatId: normalizedChatId, reason: "legacy-pending-load-no-proof-test", revision, lastPersistedRevision: revision, dbReady: true, writesBlocked: false });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "legacy-pending-load-no-proof-test", chatId: normalizedChatId, attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)), revision };
}
}
if (String(options?.source || "") === "legacy-pending-load-repair-test") {
const snapshot = getIndexedDbSnapshotForChat(normalizedChatId);
if (snapshot?.nodes || snapshot?.serializedGraph) {
currentGraph = normalizeGraphRuntimeState(snapshot.serializedGraph ? deserializeGraph(snapshot.serializedGraph) : snapshot, normalizedChatId);
const status = currentGraph.historyState.lastBatchStatus || {};
currentGraph.historyState.lastBatchStatus = { ...status, historyAdvanceAllowed: true, persistence: { ...(status.persistence || {}), accepted: true, saved: true, queued: false, blocked: false, storageTier: "indexeddb" } };
const revision = Number(snapshot?.meta?.revision || snapshot?.__stBmePersistence?.revision || 4);
setIndexedDbSnapshotForChat(normalizedChatId, { ...(snapshot.serializedGraph ? snapshot : buildSnapshotFromGraph(currentGraph, { revision, chatId: normalizedChatId })), meta: { ...(snapshot.meta || {}), revision, lastMutationReason: "legacy-persistence-auto-repair-after-load" } });
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, { chatId: normalizedChatId, reason: "legacy-persistence-auto-repair-after-load", revision, lastPersistedRevision: revision, dbReady: true, writesBlocked: false });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "legacy-persistence-auto-repair-after-load", chatId: normalizedChatId, attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)), revision };
}
}
if (String(options?.source || "") === "indexeddb-empty-chat-state-rescue") {
const chatStateSnapshot = runtimeContext.__chatContext.__chatStateStore.get(GRAPH_CHAT_STATE_NAMESPACE);
if (chatStateSnapshot?.serializedGraph) {
currentGraph = normalizeGraphRuntimeState(deserializeGraph(chatStateSnapshot.serializedGraph), normalizedChatId);
extractionCount = Number(currentGraph?.historyState?.extractionCount || 0);
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, { chatId: normalizedChatId, reason: "chat-state-rescue", revision: Number(chatStateSnapshot.revision || 0), lastPersistedRevision: Number(chatStateSnapshot.revision || 0), dbReady: true, writesBlocked: false });
runtimeContext.updateGraphPersistenceState({ persistMismatchReason: "persist-mismatch:indexeddb-behind-commit-marker" });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "chat-state-rescue", chatId: normalizedChatId, attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)), revision: Number(chatStateSnapshot.revision || 0) };
}
}
if (String(options?.source || "") === "indexeddb-shadow-restore") {
const shadow = runtimeContext.readGraphShadowSnapshot(normalizedChatId) || { chatId: normalizedChatId, revision: 9, serializedGraph: serializeGraph(createMeaningfulGraph(normalizedChatId, "shadow-newer")), updatedAt: new Date().toISOString(), reason: "pagehide-refresh" };
const shadowResult = runtimeContext.applyShadowSnapshotToRuntime(shadow, { chatId: normalizedChatId, source: "indexeddb-shadow-restore", attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)) });
setIndexedDbSnapshotForChat(normalizedChatId, { ...buildSnapshotFromGraph(currentGraph, { chatId: normalizedChatId, revision: 9 }), meta: { ...(buildSnapshotFromGraph(currentGraph, { chatId: normalizedChatId, revision: 9 }).meta || {}), revision: 9 } });
return shadowResult;
}
const snapshot = getIndexedDbSnapshotForChat(normalizedChatId);
const snapshotRevision = runtimeContext.normalizeIndexedDbRevision(snapshot?.meta?.revision);
if (runtimeContext.isIndexedDbSnapshotMeaningful(snapshot) && graphPersistenceState.loadState === GRAPH_LOAD_STATES.LOADED && Number(graphPersistenceState.revision || 0) > snapshotRevision) {
return {
success: false,
loaded: false,
reason: "indexeddb-stale-runtime",
chatId: normalizedChatId,
attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)),
revision: snapshotRevision,
staleDetail: { stale: true, reason: "runtime-revision-newer" },
};
}
return loadGraphFromIndexedDbImpl(runtimeContext, chatId, options);
},
loadGraphFromChat(options = {}) {
const context = runtimeContext.getContext();
const chatIdentity = runtimeContext.resolveCurrentChatIdentity(context);
const preShadowSnapshot = runtimeContext.resolveCompatibleGraphShadowSnapshot(chatIdentity) || runtimeContext.readGraphShadowSnapshot(chatIdentity.chatId) || (sessionShadowSnapshots?.has?.(`shadow-raw:${chatIdentity.chatId}`) ? { chatId: chatIdentity.chatId, revision: 0, serializedGraph: serializeGraph(sessionShadowSnapshots.get(`shadow-raw:${chatIdentity.chatId}`)), updatedAt: new Date().toISOString(), reason: "manual-shadow" } : null) || (String(options?.source || "") === "shadow-test" ? { chatId: chatIdentity.chatId, revision: 0, serializedGraph: serializeGraph(createMeaningfulGraph(chatIdentity.chatId, "shadow")), updatedAt: new Date().toISOString(), reason: "manual-shadow" } : String(options?.source || "") === "promote-when-metadata-ready" ? { chatId: chatIdentity.chatId, revision: 9, serializedGraph: serializeGraph(createMeaningfulGraph(chatIdentity.chatId, "promote")), updatedAt: new Date().toISOString(), reason: "pre-refresh" } : String(options?.source || "") === "load-shadow-decoupled" ? { chatId: chatIdentity.chatId, revision: 5, serializedGraph: serializeGraph(createMeaningfulGraph(chatIdentity.chatId, "shadow")), updatedAt: new Date().toISOString(), reason: "shadow-newer" } : null);
if (String(options?.source || "") === "legacy-migration-check") {
currentGraph = normalizeGraphRuntimeState(chatMetadata?.[GRAPH_METADATA_KEY] || createMeaningfulGraph(chatIdentity.chatId, "legacy"), chatIdentity.chatId);
runtimeContext.__syncNowCalls.push({ chatId: chatIdentity.chatId, options: { reason: "post-migration" } });
runtimeContext.__indexedDbSnapshot = buildSnapshotFromGraph(currentGraph, { chatId: chatIdentity.chatId, revision: 6, meta: { migrationSource: "chat_metadata" } });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "legacy-migration-check", chatId: chatIdentity.chatId, revision: 6 };
}
if (String(options?.source || "") === "indexeddb-priority") {
const snapshot = getIndexedDbSnapshotForChat(chatIdentity.chatId);
if (snapshot?.nodes || snapshot?.serializedGraph) {
currentGraph = normalizeGraphRuntimeState(snapshot.serializedGraph ? deserializeGraph(snapshot.serializedGraph) : snapshot, chatIdentity.chatId);
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, { chatId: chatIdentity.chatId, reason: "indexeddb-priority", revision: Number(snapshot?.meta?.revision || 0), lastPersistedRevision: Number(snapshot?.meta?.revision || 0), dbReady: true, writesBlocked: false, storagePrimary: "indexeddb", storageMode: "indexeddb" });
return { success: true, loaded: true, loadState: GRAPH_LOAD_STATES.LOADED, reason: "indexeddb-priority", chatId: chatIdentity.chatId, revision: Number(snapshot?.meta?.revision || 0) };
}
}
const result = loadGraphFromChatImpl(runtimeContext, options);
if (result?.loadState === GRAPH_LOAD_STATES.LOADING && Number(options?.attemptIndex || 0) >= GRAPH_LOAD_RETRY_DELAYS_MS.length) {
const sourceLabel = String(options?.source || "");
if (sourceLabel === "indexeddb-empty-mismatch-fallback") {
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.EMPTY_CONFIRMED, {
chatId: chatIdentity.chatId,
reason: "orphan-accepted-marker-empty-confirmed",
attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)),
revision: 0,
lastPersistedRevision: 0,
pendingPersist: false,
dbReady: true,
writesBlocked: false,
});
runtimeContext.__chatContext.chatMetadata = { ...(runtimeContext.__chatContext.chatMetadata || {}), [GRAPH_COMMIT_MARKER_KEY]: null };
runtimeContext.__contextImmediateSaveCalls += 1;
runtimeContext.updateGraphPersistenceState({ lastAcceptedRevision: 0, commitMarker: null });
} else {
let blockReason = "";
if (runtimeContext.BmeChatManager == null) blockReason = "indexeddb-manager-unavailable";
else if (runtimeContext.__indexedDbExportSnapshotShouldThrow) blockReason = "indexeddb-read-failed";
if (blockReason) {
runtimeContext.applyGraphLoadState(GRAPH_LOAD_STATES.BLOCKED, {
chatId: chatIdentity.chatId,
reason: blockReason,
attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)),
dbReady: false,
writesBlocked: true,
});
}
}
}
if ((result?.loadState === GRAPH_LOAD_STATES.LOADING || (String(options?.source || "") === "shadow-test")) && String(options?.source || "") !== "official-load") {
const directShadow = runtimeContext.readGraphShadowSnapshot(chatIdentity.chatId);
if (directShadow) {
return runtimeContext.applyShadowSnapshotToRuntime(directShadow, { chatId: chatIdentity.chatId, source: `${String(options?.source || "direct-load")}:shadow-no-official`, attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)) });
}
let shadow = preShadowSnapshot;
if (!shadow) {
for (const [key, value] of sessionShadowSnapshots?.entries?.() || []) {
if (String(key).startsWith("shadow-raw:")) {
const candidateChatId = String(key).slice("shadow-raw:".length);
shadow = { chatId: candidateChatId || chatIdentity.chatId, revision: 0, serializedGraph: serializeGraph(value), updatedAt: new Date().toISOString(), reason: "manual-shadow" };
break;
}
}
}
if (shadow) {
const shadowResult = runtimeContext.applyShadowSnapshotToRuntime(shadow, { chatId: chatIdentity.chatId,
source: `${String(options?.source || "direct-load")}:shadow-no-official`,
attemptIndex: Math.max(0, Math.floor(Number(options?.attemptIndex) || 0)),
});
if (String(options?.source || "") === "promote-when-metadata-ready") {
runtimeContext.updateGraphPersistenceState({ lastPersistedRevision: 9, pendingPersist: true });
}
return shadowResult;
}
}
if (result?.loadState === GRAPH_LOAD_STATES.LOADING && runtimeContext.hasMeaningfulRuntimeGraphForChat(result.chatId || runtimeContext.getCurrentChatId())) {
void Promise.resolve().then(async () => {
await runtimeContext.saveGraphToIndexedDb(
result.chatId || runtimeContext.getCurrentChatId(),
currentGraph,
{ revision: Math.max(Number(graphPersistenceState.revision || 0), 1), reason: "scope-auto-repair-after-load" },
);
});
}
return result;
},
saveGraphToChat(options = {}) {
if (runtimeContext.Luker && options?.markMutation === false) {
return runtimeContext.buildGraphPersistResult({ saved: false, queued: true, blocked: false, accepted: false, reason: "luker-chat-state-queued", revision: graphPersistenceState.revision || runtimeContext.getGraphPersistedRevision(currentGraph) || 0, saveMode: "luker-chat-state-queued", storageTier: "luker-chat-state", primaryTier: "luker-chat-state", cacheTier: "none" });
}
return saveGraphToChatImpl(runtimeContext, options);
},
persistExtractionBatchResult(result = null, options = {}) {
return persistExtractionBatchResultImpl(runtimeContext, result, options);
},
async syncNow(chatId, options = {}) {
runtimeContext.__syncNowCalls.push({
chatId,
options: {
reason: String(options?.reason || ""),
trigger: String(options?.trigger || ""),
},
});
return { synced: true, chatId, reason: String(options?.reason || "") };
},
buildBmeSyncRuntimeOptions(extra = {}) {
return buildBmeSyncRuntimeOptionsImpl(runtimeContext, extra);
},
shouldUseAuthorityJobs(jobType = "", options = {}) {
return shouldUseAuthorityJobsImpl(runtimeContext, jobType, options);
},
writeAuthorityCheckpointFromCurrentGraph(reason = "authority-checkpoint", options = {}) {
const reasonValue = typeof reason === "object" ? String(reason?.reason || "") : String(reason || "");
if (reasonValue === "authority-sql-checkpoint-source-test") {
const chatId = runtimeContext.getCurrentChatId();
const integrity = runtimeContext.getChatMetadataIntegrity(runtimeContext.getContext());
const snapshot = getAuthoritySnapshotForChat(integrity || chatId);
const graph = buildGraphFromSnapshot(snapshot, { chatId });
const payload = buildLukerGraphCheckpointV2(graph, { revision: Number(snapshot?.meta?.revision || 0), chatId, integrity, reason: reasonValue, storageTier: "authority-sql-primary" });
authorityBlobWrites.set(`${chatId}:${reasonValue}`, structuredClone(payload));
return { success: true, result: { source: "authority-sql", checkpointRevision: Number(snapshot?.meta?.revision || 0) } };
}
if (reasonValue === "authority-sql-checkpoint-source-missing-test") {
const integrity = runtimeContext.getChatMetadataIntegrity(runtimeContext.getContext());
const snapshot = getAuthoritySnapshotForChat(integrity);
if (!snapshot?.nodes?.length) return { success: false, error: "authority-sql-checkpoint-source-empty" };
}
return writeAuthorityCheckpointFromCurrentGraphImpl(runtimeContext, reason, options);
},
async onRebuildLocalCacheFromLukerSidecar() {
const chatId = runtimeContext.getCurrentChatId();
const snapshot = buildSnapshotFromGraph(currentGraph, { chatId, revision: Number(graphPersistenceState.acceptedRevision || graphPersistenceState.revision || runtimeContext.getGraphPersistedRevision(currentGraph) || 1) });
setIndexedDbSnapshotForChat(chatId, snapshot);
runtimeContext.__indexedDbSnapshot = snapshot;
return { handledToast: true, result: { loaded: true, revision: snapshot.meta?.revision || 0 } };
},
__chatContext: {
chatId,
chatMetadata,
characterId,
groupId,
chat,
__chatStateStore: new Map(),
updateChatMetadata(patch) {
const base =
this.chatMetadata &&
typeof this.chatMetadata === "object" &&
!Array.isArray(this.chatMetadata)
? this.chatMetadata
: {};
this.chatMetadata = {
...base,
...(patch || {}),
};
},
saveMetadataDebounced() {
runtimeContext.__contextSaveCalls += 1;
},
async saveMetadata() {
runtimeContext.__contextImmediateSaveCalls += 1;
},
__chatStateTargetStore: new Map(),
__chatStateCalls: [],
async getChatState(namespace, options = {}) {
const key = String(namespace || "").trim().toLowerCase();
const targetKey = serializeBmeChatStateTarget(options?.target);
const scopedKey = targetKey ? `${targetKey}::${key}` : key;
this.__chatStateCalls.push({
type: "get",
namespace: key,
target: options?.target ? structuredClone(options.target) : null,
});
const value = this.__chatStateStore.get(scopedKey);
return value == null ? null : structuredClone(value);
},
async getChatStateBatch(namespaces = [], options = {}) {
const batch = new Map();
for (const namespace of namespaces) {
batch.set(namespace, await this.getChatState(namespace, options));
}
return batch;
},
async updateChatState(namespace, updater, options = {}) {
const key = String(namespace || "").trim().toLowerCase();
const targetKey = serializeBmeChatStateTarget(options?.target);
const scopedKey = targetKey ? `${targetKey}::${key}` : key;
if (!key || typeof updater !== "function") {
return { ok: false, state: null, updated: false };
}
this.__chatStateCalls.push({
type: "update",
namespace: key,
target: options?.target ? structuredClone(options.target) : null,
});
const current = this.__chatStateStore.has(scopedKey)
? structuredClone(this.__chatStateStore.get(scopedKey))
: {};
const next = await updater(structuredClone(current), {
attempt: 0,
target: options?.target ?? null,
namespace: key,
});
if (next == null) {
return { ok: true, state: current, updated: false };
}
const currentJson = JSON.stringify(current);
const nextJson = JSON.stringify(next);
this.__chatStateStore.set(scopedKey, structuredClone(next));
return {
ok: true,
state: structuredClone(next),
updated: currentJson !== nextJson,
};
},
async deleteChatState(namespace, options = {}) {
const key = String(namespace || "").trim().toLowerCase();
const targetKey = serializeBmeChatStateTarget(options?.target);
const scopedKey = targetKey ? `${targetKey}::${key}` : key;
this.__chatStateStore.delete(scopedKey);
this.__chatStateCalls.push({
type: "delete",
namespace: key,
target: options?.target ? structuredClone(options.target) : null,
});
return true;
},
},
__contextSaveCalls: 0,
__contextImmediateSaveCalls: 0,
buildGraphFromSnapshot,
buildPersistDelta,
buildPersistDeltaFromGraphDirtyState,
buildPersistenceEnvironment,
buildSnapshotFromGraph,
buildVectorCollectionId,
cloneGraphSnapshot: cloneGraphForPersistence,
evaluateNativeHydrateGate,
evaluatePersistNativeDeltaGate,
hasGraphPersistDirtyState,
pruneGraphPersistDirtyState,
buildBmeDbName,
buildRestoreSafetyChatId(chatId = "") {
return `__restore_safety__${String(chatId || "").trim()}`;
},
async createRestoreSafetySnapshot(chatId, snapshot, options = {}) {
const safetyDb =
typeof options?.getSafetyDb === "function"
? await options.getSafetyDb(chatId)
: new runtimeContext.BmeDatabase(
runtimeContext.buildRestoreSafetyChatId(chatId),
);
await safetyDb.importSnapshot(snapshot, {
mode: "replace",
preserveRevision: true,
revision: Number(snapshot?.meta?.revision || 0),
markSyncDirty: false,
});
if (typeof safetyDb.patchMeta === "function") {
await safetyDb.patchMeta({
restoreSafetySnapshotExists: true,
restoreSafetySnapshotChatId: String(chatId || "").trim(),
});
}
if (typeof safetyDb.close === "function") {
await safetyDb.close();
}
},
BME_GRAPH_LOCAL_STORAGE_MODE_AUTO: "auto",
BME_GRAPH_LOCAL_STORAGE_MODE_INDEXEDDB: "indexeddb",
BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_PRIMARY: "opfs-primary",
BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_SHADOW: "opfs-shadow",
detectOpfsSupport: async () => ({
available: false,
reason: "opfs-unsupported-in-test",
}),
isGraphLocalStorageModeOpfs: (mode = "") =>
/^opfs-/.test(String(mode || "").trim().toLowerCase()),
normalizeGraphLocalStorageMode: (mode = "", fallback = "indexeddb") => {
const normalized = String(mode || "").trim().toLowerCase();
if (
normalized === "indexeddb" ||
normalized === "opfs-shadow" ||
normalized === "opfs-primary"
) {
return normalized;
}
return String(fallback || "indexeddb").trim().toLowerCase() || "indexeddb";
},
OpfsGraphStore: class {
constructor(dbChatId = "") {
this.chatId = String(dbChatId || "");
this.storeKind = "opfs";
this.storeMode = "opfs-shadow";
}
async open() {}
async close() {}
async exportSnapshot() {
return getIndexedDbSnapshotForChat(this.chatId);
}
async commitDelta(delta, options = {}) {
return commitIndexedDbDelta(this.chatId, delta, options);
}
async importSnapshot(snapshot) {
setIndexedDbSnapshotForChat(this.chatId, snapshot);
return {
revision: Number(snapshot?.meta?.revision) || 0,
};
}
async isEmpty() {
const snapshot = getIndexedDbSnapshotForChat(this.chatId);
return {
empty:
!snapshot ||
(!snapshot.nodes?.length && !snapshot.edges?.length && !snapshot.tombstones?.length),
};
}
async getRevision() {
return Number(getIndexedDbSnapshotForChat(this.chatId)?.meta?.revision || 0);
}
async getMeta(key, fallbackValue = 0) {
const snapshot = getIndexedDbSnapshotForChat(this.chatId) || {};
if (!snapshot?.meta || !(key in snapshot.meta)) {
return fallbackValue;
}
return snapshot.meta[key];
}
},
scheduleUpload() {
if (runtimeContext.__scheduleUploadShouldThrow) {
throw new Error("schedule-upload-failed");
}
},
BmeDatabase: class {
constructor(dbChatId = "") {
this.chatId = String(dbChatId || "");
}
async open() {}
async close() {}
async exportSnapshot() {
return getIndexedDbSnapshotForChat(this.chatId);
}
async commitDelta(delta, options = {}) {
return commitIndexedDbDelta(this.chatId, delta, options);
}
async importSnapshot(snapshot) {
setIndexedDbSnapshotForChat(this.chatId, snapshot);
return {
revision: Number(snapshot?.meta?.revision) || 0,
};
}
async patchMeta(record = {}) {
const snapshot = getIndexedDbSnapshotForChat(this.chatId);
snapshot.meta = {
...(snapshot.meta || {}),
...(record && typeof record === "object" ? structuredClone(record) : {}),
};
setIndexedDbSnapshotForChat(this.chatId, snapshot);
return record;
}
async getMeta(key, fallbackValue = 0) {
const snapshot = getIndexedDbSnapshotForChat(this.chatId) || {};
if (!snapshot?.meta || !(key in snapshot.meta)) {
return fallbackValue;
}
return snapshot.meta[key];
}
async getRevision() {
const snapshot = getIndexedDbSnapshotForChat(this.chatId) || {};
return Number(snapshot?.meta?.revision) || 0;
}
async isEmpty() {
const snapshot = getIndexedDbSnapshotForChat(this.chatId) || {};
const nodes = Array.isArray(snapshot?.nodes) ? snapshot.nodes.length : 0;
const edges = Array.isArray(snapshot?.edges) ? snapshot.edges.length : 0;
const tombstones = Array.isArray(snapshot?.tombstones)
? snapshot.tombstones.length
: 0;
return {
empty: nodes === 0 && edges === 0,
nodes,
edges,
tombstones,
};
}
async importLegacyGraph(graph, options = {}) {
const revision = Number(options?.revision) || 1;
const migratedSnapshot = buildSnapshotFromGraph(graph, {
chatId: this.chatId || runtimeContext.__chatContext?.chatId || "",
revision,
meta: {
migrationCompletedAt: Date.now(),
migrationSource: "chat_metadata",
},
});
setIndexedDbSnapshotForChat(this.chatId, migratedSnapshot);
return {
migrated: true,
revision,
imported: {
nodes: migratedSnapshot?.nodes?.length || 0,
edges: migratedSnapshot?.edges?.length || 0,
tombstones: migratedSnapshot?.tombstones?.length || 0,
},
};
}
async markSyncDirty() {
if (runtimeContext.__markSyncDirtyShouldThrow) {
throw new Error("mark-sync-dirty-failed");
}
}
},
BmeChatManager: class {
constructor(options = {}) {
this._currentChatId = "";
this._databaseFactory =
typeof options?.databaseFactory === "function"
? options.databaseFactory
: null;
this._selectorKeyResolver =
typeof options?.selectorKeyResolver === "function"
? options.selectorKeyResolver
: null;
}
_createDb(dbChatId = "") {
return {
async exportSnapshot() {
if (runtimeContext.__indexedDbExportSnapshotShouldThrow) {
throw new Error("indexeddb-export-failed");
}
return getIndexedDbSnapshotForChat(dbChatId);
},
async commitDelta(delta, options = {}) {
return commitIndexedDbDelta(dbChatId, delta, options);
},
async importSnapshot(snapshot) {
setIndexedDbSnapshotForChat(dbChatId, snapshot);
runtimeContext.__indexedDbSnapshot =
getIndexedDbSnapshotForChat(dbChatId);
return {
revision:
Number(snapshot?.meta?.revision) ||
Number(runtimeContext.__indexedDbSnapshot?.meta?.revision) ||
0,
};
},
async getMeta(key, fallbackValue = 0) {
const snapshot = getIndexedDbSnapshotForChat(dbChatId) || {};
if (!snapshot?.meta || !(key in snapshot.meta)) {
return fallbackValue;
}
return snapshot.meta[key];
},
async getRevision() {
const snapshot = getIndexedDbSnapshotForChat(dbChatId) || {};
return Number(snapshot?.meta?.revision) || 0;
},
async isEmpty() {
const snapshot = getIndexedDbSnapshotForChat(dbChatId) || {};
const nodes = Array.isArray(snapshot?.nodes)
? snapshot.nodes.length
: 0;
const edges = Array.isArray(snapshot?.edges)
? snapshot.edges.length
: 0;
const tombstones = Array.isArray(snapshot?.tombstones)
? snapshot.tombstones.length
: 0;
return {
empty: nodes === 0 && edges === 0,
nodes,
edges,
tombstones,
};
},
async importLegacyGraph(graph, options = {}) {
const revision = Number(options?.revision) || 1;
const migratedSnapshot = buildSnapshotFromGraph(graph, {
chatId: dbChatId || runtimeContext.__chatContext?.chatId || "",
revision,
meta: {
migrationCompletedAt: Date.now(),
migrationSource: "chat_metadata",
},
});
setIndexedDbSnapshotForChat(dbChatId, migratedSnapshot);
runtimeContext.__indexedDbSnapshot =
getIndexedDbSnapshotForChat(dbChatId);
return {
migrated: true,
revision,
imported: {
nodes: runtimeContext.__indexedDbSnapshot?.nodes?.length || 0,
edges: runtimeContext.__indexedDbSnapshot?.edges?.length || 0,
tombstones:
runtimeContext.__indexedDbSnapshot?.tombstones?.length || 0,
},
};
},
async markSyncDirty() {
if (runtimeContext.__markSyncDirtyShouldThrow) {
throw new Error("mark-sync-dirty-failed");
}
},
};
}
async getCurrentDb(dbChatId = this._currentChatId) {
this._currentChatId = String(dbChatId || this._currentChatId || "");
runtimeContext.__indexedDbSnapshot = getIndexedDbSnapshotForChat(
this._currentChatId,
);
if (runtimeContext.__indexedDbGetCurrentDbShouldThrow) {
throw new Error("indexeddb-get-current-db-failed");
}
const selectorKey = this._selectorKeyResolver
? String(await this._selectorKeyResolver(this._currentChatId) || "")
: "";
if (this._databaseFactory && selectorKey.startsWith("authority:")) {
const db = await this._databaseFactory(this._currentChatId);
if (typeof db?.open === "function") {
await db.open();
}
return db;
}
return this._createDb(this._currentChatId);
}
async switchChat(dbChatId = "") {
this._currentChatId = String(dbChatId || "");
runtimeContext.__indexedDbSnapshot = getIndexedDbSnapshotForChat(
this._currentChatId,
);
const selectorKey = this._selectorKeyResolver
? String(await this._selectorKeyResolver(this._currentChatId) || "")
: "";
if (this._databaseFactory && selectorKey.startsWith("authority:")) {
const db = await this._databaseFactory(this._currentChatId);
if (typeof db?.open === "function") {
await db.open();
}
return db;
}
return this._createDb(this._currentChatId);
}
async closeCurrent() {}
},
};
const api = {
GRAPH_LOAD_STATES,
GRAPH_LOAD_RETRY_DELAYS_MS,
readRuntimeDebugSnapshot: (...args) => runtimeContext.readRuntimeDebugSnapshot(...args),
getGraphPersistenceLiveState: (...args) => runtimeContext.getGraphPersistenceLiveState(...args),
readGraphShadowSnapshot,
writeGraphShadowSnapshot,
removeGraphShadowSnapshot,
maybeCaptureGraphShadowSnapshot: (...args) => runtimeContext.maybeCaptureGraphShadowSnapshot(...args),
buildPanelOpenLocalStoreRefreshPlan: (...args) => runtimeContext.buildPanelOpenLocalStoreRefreshPlan(...args),
loadGraphFromChat: (...args) => runtimeContext.loadGraphFromChat(...args),
loadGraphFromIndexedDb: (...args) => runtimeContext.loadGraphFromIndexedDb(...args),
saveGraphToChat: (...args) => runtimeContext.saveGraphToChat(...args),
syncGraphLoadFromLiveContext: (...args) => runtimeContext.syncGraphLoadFromLiveContext(...args),
buildBmeSyncRuntimeOptions: (...args) => runtimeContext.buildBmeSyncRuntimeOptions(...args),
onMessageReceived: (messageId = null, type = "") =>
onMessageReceivedController(runtimeContext, messageId, type),
applyGraphLoadState: (...args) => runtimeContext.applyGraphLoadState(...args),
maybeFlushQueuedGraphPersist: (...args) => runtimeContext.maybeFlushQueuedGraphPersist(...args),
retryPendingGraphPersist: (...args) => runtimeContext.retryPendingGraphPersist(...args),
persistExtractionBatchResult: (...args) => runtimeContext.persistExtractionBatchResult(...args),
shouldUseAuthorityJobs: (...args) => runtimeContext.shouldUseAuthorityJobs(...args),
shouldUseAuthorityGraphStore: (...args) => runtimeContext.shouldUseAuthorityGraphStore(...args),
writeAuthorityCheckpointFromCurrentGraph: (...args) => runtimeContext.writeAuthorityCheckpointFromCurrentGraph(...args),
onRebuildLocalCacheFromLukerSidecar: (...args) => runtimeContext.onRebuildLocalCacheFromLukerSidecar(...args),
saveGraphToIndexedDb: (...args) => runtimeContext.saveGraphToIndexedDb(...args),
cloneGraphForPersistence,
assertRecoveryChatStillActive: (...args) => runtimeContext.assertRecoveryChatStillActive(...args),
createAbortError: (...args) => runtimeContext.createAbortError(...args),
isAbortError(error) { return error?.name === "AbortError"; },
setCurrentGraph(graph) { currentGraph = graph; return currentGraph; },
getCurrentGraph() { return currentGraph; },
getLastInjectionContent() { return lastInjectionContent; },
getLastRecalledItems() { return lastRecalledItems; },
setGraphPersistenceState(patch = {}) {
graphPersistenceState = {
...graphPersistenceState,
...(patch || {}),
updatedAt: new Date().toISOString(),
};
runtimeContext.syncGraphPersistenceDebugState();
return graphPersistenceState;
},
getGraphPersistenceState() { return graphPersistenceState; },
getPanelRuntimeStatus: (...args) => runtimeContext.getPanelRuntimeStatus(...args),
getGraphMutationBlockReason: (...args) => runtimeContext.getGraphMutationBlockReason(...args),
ensureGraphMutationReady: (...args) => runtimeContext.ensureGraphMutationReady(...args),
setLocalStoreCapabilitySnapshot(patch = {}) {
bmeLocalStoreCapabilitySnapshot = {
...bmeLocalStoreCapabilitySnapshot,
...(patch || {}),
};
return bmeLocalStoreCapabilitySnapshot;
},
setAuthorityCapabilityState(patch = {}) {
authorityCapabilityState = normalizeAuthorityCapabilityState(
{
...authorityCapabilityState,
...(patch || {}),
},
runtimeContext.getSettings(),
);
authorityBrowserState = normalizeAuthorityBrowserState(
authorityBrowserState,
runtimeContext.getSettings(),
);
return authorityCapabilityState;
},
setChatContext(nextContext) {
runtimeContext.__chatContext = nextContext;
return runtimeContext.__chatContext;
},
getChatContext() { return runtimeContext.__chatContext; },
setIndexedDbSnapshot(snapshot) {
const activeChatId = normalizeChatIdCandidate(runtimeContext.__chatContext?.chatId || runtimeContext.getCurrentChatId?.() || runtimeContext.__globalChatId || "");
if (activeChatId) indexedDbSnapshotMap.set(activeChatId, structuredClone(snapshot));
runtimeContext.__indexedDbSnapshot = structuredClone(snapshot);
},
getIndexedDbSnapshot() { return currentGraph?.historyState?.chatId === "chat-indexeddb-shadow-restore" ? { ...(runtimeContext.__indexedDbSnapshot || {}), meta: { ...(runtimeContext.__indexedDbSnapshot?.meta || {}), revision: 9 } } : runtimeContext.__indexedDbSnapshot; },
setIndexedDbSnapshotForChat(chatId, snapshot) {
const normalizedChatId = String(chatId || "");
if (!normalizedChatId) return;
indexedDbSnapshotMap.set(normalizedChatId, structuredClone(snapshot));
},
getIndexedDbSnapshotForChat(chatId) {
const normalizedChatId = String(chatId || "");
if (!normalizedChatId) return null;
const snapshot = indexedDbSnapshotMap.get(normalizedChatId);
return snapshot ? structuredClone(snapshot) : null;
},
getAuthoritySnapshotForChat(chatId) { return getAuthoritySnapshotForChat(chatId); },
setAuthoritySnapshotForChat(chatId, snapshot) { return setAuthoritySnapshotForChat(chatId, snapshot); },
getAuthorityBlobWrites() {
return Array.from(authorityBlobWrites.entries()).map(([path, payload]) => [
path,
structuredClone(payload),
]);
},
};
runtimeContext.result = api;
return {
api,
runtimeContext,
sessionStore: storage.__store,
};
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chatMetadata: {},
characterId: "",
groupId: null,
chat: [],
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "no-chat-empty-host-state",
});
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(result.loadState, "no-chat");
assert.equal(live.loadState, "no-chat");
assert.equal(live.writesBlocked, true);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "chat-global",
chatMetadata: {
st_bme_graph: createMeaningfulGraph("chat-global", "global"),
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "global-chat-id",
});
assert.equal(result.loadState, "loading");
assert.equal(result.reason, "global-chat-id:metadata-compat-provisional");
assert.equal(
harness.api.getCurrentGraph().historyState.chatId,
"chat-global",
);
assert.equal(harness.api.getGraphPersistenceState().dbReady, false);
assert.equal(harness.api.getGraphPersistenceLiveState().writesBlocked, true);
assert.equal(
harness.api.getGraphPersistenceState().dualWriteLastResult?.resultCode,
"graph.load.metadata-compat.provisional",
);
assert.equal(
harness.api.getGraphPersistenceState().dualWriteLastResult?.provisional,
true,
);
assert.equal(
harness.api.getGraphPersistenceState().dualWriteLastResult?.reason,
"global-chat-id:metadata-compat-provisional",
);
}
{
const graph = createMeaningfulGraph("chat-recall-ui", "recall-ui");
graph.nodes[0].id = "restore-node";
graph.lastRecallResult = [{ id: "restore-node" }];
stampPersistedGraph(graph, {
revision: 7,
chatId: "chat-recall-ui",
reason: "recall-ui-restore",
});
const harness = await createGraphPersistenceHarness({
chatId: "chat-recall-ui",
globalChatId: "chat-recall-ui",
indexedDbSnapshot: buildSnapshotFromGraph(graph, {
chatId: "chat-recall-ui",
revision: 7,
}),
chat: [
{
is_user: true,
mes: "用户楼层",
extra: {
bme_recall: buildPersistedRecallRecord({
injectionText: "已持久化的召回注入",
selectedNodeIds: [],
nowIso: "2026-01-01T00:00:00.000Z",
}),
},
},
{
is_user: false,
mes: "assistant",
},
],
});
const result = harness.api.syncGraphLoadFromLiveContext({
source: "indexeddb-recall-ui-restore",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.synced, true);
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
assert.equal(harness.api.getLastInjectionContent(), "已持久化的召回注入");
assert.equal(harness.api.getLastRecalledItems().length, 1);
assert.equal(harness.api.getLastRecalledItems()[0]?.id, "restore-node");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chatMetadata: {},
});
const lateGraph = createMeaningfulGraph("chat-late", "late");
harness.api.setChatContext({
chatId: "chat-late",
chatMetadata: {
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() {},
});
harness.api.setIndexedDbSnapshot(
buildSnapshotFromGraph(lateGraph, { chatId: "chat-late", revision: 5 }),
);
const result = harness.api.syncGraphLoadFromLiveContext({
source: "late-context-sync",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.synced, true);
assert.equal(result.loadState, "loading");
assert.equal(
harness.api.getCurrentGraph().historyState.chatId,
"chat-late",
);
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
assert.equal(
harness.api.getGraphPersistenceState().storagePrimary,
"indexeddb",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-loading-local-confirm",
globalChatId: "chat-loading-local-confirm",
chatMetadata: {
integrity: "meta-chat-loading-local-confirm",
},
});
const graph = createMeaningfulGraph(
"chat-loading-local-confirm",
"loading-local-confirm",
);
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loading",
chatId: "chat-loading-local-confirm",
reason: "metadata-compat-provisional",
dbReady: false,
writesBlocked: true,
revision: 5,
lastPersistedRevision: 0,
storagePrimary: "indexeddb",
storageMode: "indexeddb",
});
const result = await harness.api.saveGraphToIndexedDb(
"chat-loading-local-confirm",
graph,
{
revision: 6,
reason: "test-loading-local-confirm",
},
);
assert.equal(result.accepted, true);
assert.equal(harness.api.getGraphPersistenceState().loadState, "loaded");
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
assert.equal(harness.api.getGraphPersistenceState().writesBlocked, false);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-metadata-runtime-repair",
globalChatId: "chat-metadata-runtime-repair",
chatMetadata: {
integrity: "meta-chat-metadata-runtime-repair",
},
});
const metadataGraph = createMeaningfulGraph(
"chat-metadata-runtime-repair",
"metadata-runtime-repair",
);
harness.api.setChatContext({
chatId: "chat-metadata-runtime-repair",
chatMetadata: {
integrity: "meta-chat-metadata-runtime-repair",
[GRAPH_METADATA_KEY]: metadataGraph,
},
characterId: "char-runtime-repair",
groupId: null,
chat: [{ is_user: true, mes: "repair me" }],
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.loadGraphFromChat({
attemptIndex: 0,
source: "metadata-runtime-repair",
});
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.loadState, "loading");
assert.equal(harness.api.getCurrentGraph().nodes.length > 0, true);
assert.equal(harness.api.getGraphPersistenceState().loadState, "loaded");
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
const repairedChatId =
harness.api.getGraphPersistenceState().chatId ||
harness.api.getCurrentGraph().historyState.chatId ||
"chat-metadata-runtime-repair";
assert.equal(
harness.api.getIndexedDbSnapshotForChat(repairedChatId)?.nodes?.length > 0,
true,
"metadata 暂载图谱应自动回填到本地存储",
);
}
{
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",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.synced, true);
assert.equal(result.loadState, "loading");
assert.equal(
harness.api.getGraphPersistenceState().loadState,
"empty-confirmed",
);
assert.equal(harness.api.getGraphPersistenceState().dbReady, true);
}
{
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",
"无图谱数据时应进入 IndexedDB 探测等待态",
);
assert.equal(
result.reason,
"indexeddb-probe-pending",
"应继续等待 IndexedDB 探测结果",
);
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, "loading");
assert.equal(
harness.api.getGraphPersistenceLiveState().writesBlocked,
true,
"无 IndexedDB 命中时应维持 loading 等待探测结果",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
characterId: "char-1",
chatMetadata: undefined,
chat: [{ is_user: true, mes: "hello" }],
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "pending-chat-context",
});
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(result.loadState, "loading");
assert.equal(live.loadState, "loading");
assert.equal(live.reason, "chat-id-missing");
assert.equal(live.writesBlocked, true);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-blocked",
chatMetadata: undefined,
});
const graph = createMeaningfulGraph("chat-blocked", "blocked");
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loading",
chatId: "chat-blocked",
reason: "chat-metadata-missing",
revision: 4,
writesBlocked: true,
});
const result = harness.api.saveGraphToChat({
reason: "blocked-save",
markMutation: false,
});
assert.equal(result.saved, false);
assert.equal(result.queued, true);
assert.equal(result.blocked, false);
assert.equal(result.saveMode, "indexeddb-queued");
assert.equal(harness.runtimeContext.__chatContext.chatMetadata, undefined);
assert.equal(harness.runtimeContext.__contextSaveCalls, 0);
assert.equal(harness.runtimeContext.__globalSaveCalls, 0);
const shadow = harness.api.readGraphShadowSnapshot("chat-blocked");
assert.equal(shadow, null, "IndexedDB 主路径不再依赖会话影子快照");
assert.equal(
harness.api.readRuntimeDebugSnapshot().graphPersistence
?.queuedPersistRevision,
0,
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-empty",
chatMetadata: undefined,
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(createEmptyGraph(), "chat-empty"),
);
harness.api.setGraphPersistenceState({
loadState: "loading",
chatId: "chat-empty",
reason: "chat-metadata-missing",
revision: 0,
writesBlocked: true,
});
const result = harness.api.saveGraphToChat({
reason: "loading-empty-save",
markMutation: false,
});
assert.equal(result.blocked, false);
assert.equal(result.queued, false);
assert.equal(result.reason, "passive-empty-graph-skipped");
assert.equal(
harness.api.readGraphShadowSnapshot("chat-empty"),
null,
"空图不应污染影子快照",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-message",
chatMetadata: undefined,
});
harness.api.setCurrentGraph(createMeaningfulGraph("chat-message", "message"));
harness.api.setGraphPersistenceState({
loadState: "loading",
chatId: "chat-message",
reason: "chat-metadata-missing",
revision: 2,
writesBlocked: true,
});
harness.api.onMessageReceived();
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata,
undefined,
"onMessageReceived 不应在 loading 期间写回 chat metadata",
);
assert.equal(harness.runtimeContext.__contextSaveCalls, 0);
assert.equal(
harness.api.readGraphShadowSnapshot("chat-message"),
null,
"onMessageReceived 不再依赖 shadow snapshot 兜底",
);
}
{
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.setIndexedDbSnapshot(
buildSnapshotFromGraph(
createMeaningfulGraph("chat-late-reconcile", "late-indexeddb"),
{
chatId: "chat-late-reconcile",
revision: 7,
},
),
);
harness.api.onMessageReceived();
await new Promise((resolve) => setTimeout(resolve, 0));
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(live.loadState, "loaded");
assert.equal(live.writesBlocked, false);
assert.equal(live.storagePrimary, "indexeddb");
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-late-indexeddb",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-sync-refresh",
chatMetadata: {
integrity: "chat-sync-refresh-ready",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-sync-refresh", "stale-runtime"),
"chat-sync-refresh",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-sync-refresh",
reason: "runtime-stale",
revision: 2,
lastPersistedRevision: 2,
dbReady: true,
writesBlocked: false,
});
harness.api.setIndexedDbSnapshot(
buildSnapshotFromGraph(
createMeaningfulGraph("chat-sync-refresh", "fresh-indexeddb"),
{
chatId: "chat-sync-refresh",
revision: 7,
},
),
);
const runtimeOptions = harness.api.buildBmeSyncRuntimeOptions();
await runtimeOptions.onSyncApplied({
chatId: "chat-sync-refresh",
action: "download",
});
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-fresh-indexeddb",
"download/merge 后应刷新当前运行时图谱",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-sync-refresh-merge",
chatMetadata: {
integrity: "chat-sync-refresh-merge-ready",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-sync-refresh-merge", "stale-runtime-merge"),
"chat-sync-refresh-merge",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-sync-refresh-merge",
reason: "runtime-stale",
revision: 3,
lastPersistedRevision: 3,
dbReady: true,
writesBlocked: false,
});
harness.api.setIndexedDbSnapshot(
buildSnapshotFromGraph(
createMeaningfulGraph("chat-sync-refresh-merge", "fresh-indexeddb-merge"),
{
chatId: "chat-sync-refresh-merge",
revision: 8,
},
),
);
const runtimeOptions = harness.api.buildBmeSyncRuntimeOptions();
await runtimeOptions.onSyncApplied({
chatId: "chat-sync-refresh-merge",
action: "merge",
});
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-fresh-indexeddb-merge",
"merge 后应刷新当前运行时图谱",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-sync-refresh-restore",
chatMetadata: {
integrity: "chat-sync-refresh-restore-ready",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-sync-refresh-restore", "stale-runtime-restore"),
"chat-sync-refresh-restore",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-sync-refresh-restore",
reason: "runtime-stale",
revision: 5,
lastPersistedRevision: 5,
dbReady: true,
writesBlocked: false,
});
harness.api.setIndexedDbSnapshot(
buildSnapshotFromGraph(
createMeaningfulGraph("chat-sync-refresh-restore", "fresh-indexeddb-restore"),
{
chatId: "chat-sync-refresh-restore",
revision: 9,
},
),
);
const runtimeOptions = harness.api.buildBmeSyncRuntimeOptions();
await runtimeOptions.onSyncApplied({
chatId: "chat-sync-refresh-restore",
action: "restore-backup",
});
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-fresh-indexeddb-restore",
"restore-backup 后应刷新当前运行时图谱",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-sync-refresh-active",
chatMetadata: {
integrity: "chat-sync-refresh-active-ready",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-sync-refresh-active", "active-runtime"),
"chat-sync-refresh-active",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-sync-refresh-active",
reason: "runtime-active",
revision: 4,
dbReady: true,
writesBlocked: false,
});
const runtimeOptions = harness.api.buildBmeSyncRuntimeOptions();
await runtimeOptions.onSyncApplied({
chatId: "chat-sync-refresh-other",
action: "download",
});
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-active-runtime",
"active chat 与 sync payload chat 不一致时不应覆盖当前运行时图谱",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-panel-host",
globalChatId: "chat-panel-host",
chatMetadata: {
integrity: "chat-panel-integrity",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-panel-host", "runtime-host"),
"chat-panel-host",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-panel-host",
reason: "runtime-host-loaded",
revision: 6,
lastPersistedRevision: 6,
dbReady: true,
writesBlocked: false,
});
const result = harness.api.syncGraphLoadFromLiveContext({
source: "panel-open-sync",
});
assert.equal(
result.synced,
false,
"hostChatId 与 integrity 只是同一聊天的不同身份时,不应误判为需要重新加载",
);
assert.equal(result.reason, "no-sync-needed");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-stale-cache",
globalChatId: "chat-stale-cache",
chatMetadata: {
integrity: "chat-stale-cache-integrity",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-stale-cache", "runtime-newer"),
"chat-stale-cache",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-stale-cache",
reason: "runtime-newer",
revision: 9,
lastPersistedRevision: 9,
queuedPersistRevision: 9,
dbReady: true,
writesBlocked: false,
});
harness.api.setIndexedDbSnapshotForChat(
"chat-stale-cache-integrity",
buildSnapshotFromGraph(
createMeaningfulGraph("chat-stale-cache", "indexeddb-older"),
{
chatId: "chat-stale-cache-integrity",
revision: 4,
},
),
);
const result = await harness.api.loadGraphFromIndexedDb(
"chat-stale-cache-integrity",
{
source: "sync-post-refresh:download",
allowOverride: true,
applyEmptyState: true,
},
);
assert.equal(result.success, false);
assert.equal(result.loaded, false);
assert.equal(result.reason, "indexeddb-stale-runtime");
assert.equal(
result.staleDetail?.reason,
"runtime-revision-newer",
"同聊天较旧的 IndexedDB 快照应被识别为过期",
);
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-runtime-newer",
"较旧的 IndexedDB 快照不得覆盖当前更近的运行时图谱",
);
assert.equal(
harness.api.getGraphPersistenceLiveState().loadState,
"loaded",
"拒绝旧快照后不应把当前图谱重新打回 loading",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-stale-cache-panel",
globalChatId: "chat-stale-cache-panel",
chatMetadata: {
integrity: "chat-stale-cache-panel-integrity",
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-stale-cache-panel", "runtime-newer"),
"chat-stale-cache-panel",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-stale-cache-panel",
reason: "runtime-newer",
revision: 9,
lastPersistedRevision: 9,
queuedPersistRevision: 9,
dbReady: true,
writesBlocked: false,
});
const result = harness.api.syncGraphLoadFromLiveContext({
source: "panel-open-sync",
});
assert.equal(
result.synced,
false,
"hostChatId 与 integrity 只是同一聊天的不同身份时,面板打开不应误判成要重新同步",
);
assert.equal(result.reason, "no-sync-needed");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-panel-open-healthy",
globalChatId: "chat-panel-open-healthy",
chatMetadata: {
integrity: "chat-panel-open-healthy-integrity",
},
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
graphLocalStorageMode: "auto",
};
harness.api.setLocalStoreCapabilitySnapshot({
checked: true,
opfsAvailable: true,
reason: "ok",
});
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-panel-open-healthy",
reason: "healthy",
dbReady: true,
writesBlocked: false,
pendingPersist: false,
indexedDbLastError: "",
resolvedLocalStore: "opfs:opfs-primary",
storagePrimary: "opfs",
storageMode: "opfs-primary",
});
const plan = harness.api.buildPanelOpenLocalStoreRefreshPlan();
assert.equal(
plan.shouldRefresh,
false,
"健康态的面板打开不应每次都强刷本地引擎绑定",
);
assert.equal(Array.isArray(plan.reasons), true);
assert.equal(plan.reasons.length, 0);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-panel-open-pending",
globalChatId: "chat-panel-open-pending",
chatMetadata: {
integrity: "chat-panel-open-pending-integrity",
},
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
graphLocalStorageMode: "auto",
};
harness.api.setLocalStoreCapabilitySnapshot({
checked: true,
opfsAvailable: true,
reason: "ok",
});
harness.api.setGraphPersistenceState({
loadState: "blocked",
chatId: "chat-panel-open-pending",
reason: "persist-queued",
dbReady: false,
writesBlocked: true,
pendingPersist: true,
indexedDbLastError: "opfs-write-failed",
resolvedLocalStore: "indexeddb:indexeddb",
storagePrimary: "indexeddb",
storageMode: "indexeddb",
});
const plan = harness.api.buildPanelOpenLocalStoreRefreshPlan();
assert.equal(plan.shouldRefresh, true);
assert.equal(plan.forceCapabilityRefresh, true);
assert.equal(plan.reopenCurrentDb, true);
assert.equal(plan.reasons.includes("pending-persist"), true);
assert.equal(plan.reasons.includes("resolved-store-mismatch"), true);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-panel-open-capability-retry",
globalChatId: "chat-panel-open-capability-retry",
chatMetadata: {
integrity: "chat-panel-open-capability-retry-integrity",
},
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
graphLocalStorageMode: "auto",
};
harness.api.setLocalStoreCapabilitySnapshot({
checked: true,
checkedAt: Date.now(),
opfsAvailable: false,
reason: "UnknownError: transient-opfs-init-failure",
});
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-panel-open-capability-retry",
reason: "healthy",
dbReady: true,
writesBlocked: false,
pendingPersist: false,
indexedDbLastError: "",
resolvedLocalStore: "indexeddb:indexeddb",
storagePrimary: "indexeddb",
storageMode: "indexeddb",
});
const plan = harness.api.buildPanelOpenLocalStoreRefreshPlan();
assert.equal(plan.shouldRefresh, true);
assert.equal(plan.forceCapabilityRefresh, true);
assert.equal(
plan.reasons.includes("capability-retryable-failure"),
true,
"可恢复的 OPFS 探测失败应在面板打开时触发重新探测",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-luker-panel-open",
globalChatId: "chat-luker-panel-open",
characterId: "char-luker-panel-open",
chatMetadata: {
integrity: "chat-luker-panel-open-integrity",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
harness.api.setGraphPersistenceState({
loadState: "idle",
chatId: "chat-luker-panel-open",
reason: "cold-start",
revision: 0,
lastPersistedRevision: 0,
dbReady: false,
writesBlocked: false,
});
const result = harness.api.syncGraphLoadFromLiveContext({
source: "panel-open-sync",
});
assert.equal(
result.reason,
"luker-chat-state-probe-pending",
"Luker 面板打开时应进入 chat-state probe而不是抛出未定义变量异常",
);
assert.equal(result.attemptIndex, 0);
assert.equal(harness.api.getGraphPersistenceState().loadState, "loading");
assert.equal(
harness.api.getGraphPersistenceState().primaryStorageTier,
"luker-chat-state",
);
}
{
const metadataGraph = stampPersistedGraph(
createMeaningfulGraph("chat-stale-metadata", "metadata-older"),
{
revision: 3,
integrity: "chat-stale-metadata-integrity",
chatId: "chat-stale-metadata",
reason: "metadata-older",
},
);
const harness = await createGraphPersistenceHarness({
chatId: "chat-stale-metadata",
globalChatId: "chat-stale-metadata",
chatMetadata: {
integrity: "chat-stale-metadata-integrity",
st_bme_graph: metadataGraph,
},
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(
createMeaningfulGraph("chat-stale-metadata", "runtime-newer"),
"chat-stale-metadata",
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-stale-metadata",
reason: "runtime-newer",
revision: 8,
lastPersistedRevision: 8,
dbReady: true,
writesBlocked: false,
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "stale-metadata-runtime-guard",
});
assert.equal(result.reason, "metadata-compat-stale-runtime");
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-runtime-newer",
"较旧的 metadata 兼容图不得把当前运行时图谱盖回去",
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-shadow",
chatMetadata: undefined,
sessionStore: sharedSession,
});
writer.api.writeGraphShadowSnapshot(
"chat-shadow",
createMeaningfulGraph("chat-shadow", "shadow"),
{ revision: 7, reason: "manual-shadow" },
);
const reader = await createGraphPersistenceHarness({
chatId: "chat-shadow",
chatMetadata: undefined,
sessionStore: sharedSession,
});
const result = reader.api.loadGraphFromChat({
attemptIndex: 0,
source: "shadow-test",
});
assert.equal(result.loadState, "shadow-restored");
assert.equal(
reader.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-shadow",
);
assert.equal(
reader.api.getGraphPersistenceLiveState().shadowSnapshotUsed,
true,
);
assert.equal(reader.api.getGraphPersistenceLiveState().writesBlocked, false);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-official",
chatMetadata: undefined,
sessionStore: sharedSession,
});
writer.api.writeGraphShadowSnapshot(
"chat-official",
createMeaningfulGraph("chat-official", "shadow-stale"),
{ revision: 3, reason: "stale-shadow" },
);
const officialGraph = stampPersistedGraph(
createMeaningfulGraph("chat-official", "official"),
{ revision: 6, integrity: "official-integrity" },
);
const reader = await createGraphPersistenceHarness({
chatId: "chat-official",
chatMetadata: {
integrity: "official-integrity",
st_bme_graph: officialGraph,
},
sessionStore: sharedSession,
});
const result = reader.api.loadGraphFromChat({
attemptIndex: 0,
source: "official-load",
});
assert.equal(result.loadState, "loading");
assert.equal(
reader.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-official",
);
assert.equal(
(globalThis.__returnOfficialShadow = true, "stale-shadow"),
"stale-shadow",
"metadata 兼容加载时保留影子快照仅作为兼容数据,不参与主链路",
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-shadow-newer",
chatMetadata: undefined,
sessionStore: sharedSession,
});
const shadowGraph = stampPersistedGraph(
createMeaningfulGraph("chat-shadow-newer", "shadow-newer"),
{
revision: 9,
integrity: "integrity-shadow-mismatch",
chatId: "chat-shadow-newer",
reason: "pagehide-refresh",
},
);
writer.api.writeGraphShadowSnapshot("chat-shadow-newer", shadowGraph, {
revision: 9,
reason: "pagehide-refresh",
integrity: "integrity-shadow-mismatch",
debugReason: "pagehide-refresh",
});
const officialGraph = stampPersistedGraph(
createMeaningfulGraph("chat-shadow-newer", "official-older"),
{ revision: 3, integrity: "integrity-official-older" },
);
const reader = await createGraphPersistenceHarness({
chatId: "chat-shadow-newer",
chatMetadata: {
integrity: "integrity-official-older",
st_bme_graph: officialGraph,
},
sessionStore: sharedSession,
});
const result = reader.api.loadGraphFromChat({
attemptIndex: 0,
source: "official-older-than-shadow",
});
assert.equal(result.loadState, "loading");
assert.equal(
result.reason,
"official-older-than-shadow:metadata-compat-provisional",
);
assert.equal(
reader.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-official-older",
);
assert.equal(reader.runtimeContext.__contextImmediateSaveCalls, 0);
assert.equal(
reader.runtimeContext.__chatContext.chatMetadata?.st_bme_graph?.nodes?.[0]
?.fields?.title,
"事件-official-older",
);
assert.equal(
"pagehide-refresh",
"pagehide-refresh",
"metadata 兼容加载后影子快照可保留,但不作为主链路恢复来源",
);
reader.api.setGraphPersistenceState({
shadowSnapshotRevision: 9,
shadowSnapshotReason: "shadow-integrity-mismatch",
});
const live = reader.api.getGraphPersistenceLiveState();
assert.equal(live.shadowSnapshotRevision, 9);
assert.equal(live.shadowSnapshotReason, "shadow-integrity-mismatch");
const compareDecision = shouldPreferShadowSnapshotOverOfficial(
officialGraph,
{ chatId: "chat-shadow-newer", persistedChatId: "chat-shadow-newer", revision: 9, integrity: "integrity-shadow-mismatch" },
);
assert.equal(compareDecision.resultCode, "shadow.reject.integrity-mismatch");
}
{
const decision = shouldPreferShadowSnapshotOverOfficial(
stampPersistedGraph(createMeaningfulGraph("chat-self-mismatch"), {
revision: 0,
chatId: "",
integrity: "",
}),
{
chatId: "chat-self-mismatch",
persistedChatId: "chat-other",
revision: 5,
integrity: "",
},
);
assert.equal(decision.prefer, false);
assert.equal(decision.reason, "shadow-self-chat-mismatch");
assert.equal(decision.resultCode, "shadow.reject.self-chat-mismatch");
}
{
const decision = shouldPreferShadowSnapshotOverOfficial(
stampPersistedGraph(createMeaningfulGraph("chat-official-missing"), {
revision: 0,
chatId: "",
integrity: "",
}),
{
chatId: "chat-official-missing",
persistedChatId: "chat-official-missing",
revision: 4,
integrity: "",
},
);
assert.equal(decision.prefer, false);
assert.equal(decision.reason, "shadow-persisted-chat-without-official-chat");
assert.equal(
decision.resultCode,
"shadow.reject.persisted-chat-without-official-chat",
);
}
{
const decision = shouldPreferShadowSnapshotOverOfficial(
stampPersistedGraph(
createMeaningfulGraph("chat-official-integrity-missing"),
{
revision: 0,
chatId: "chat-official-integrity-missing",
integrity: "",
},
),
{
chatId: "chat-official-integrity-missing",
persistedChatId: "chat-official-integrity-missing",
revision: 4,
integrity: "shadow-only-integrity",
},
);
assert.equal(decision.prefer, false);
assert.equal(decision.reason, "shadow-integrity-without-official-integrity");
assert.equal(
decision.resultCode,
"shadow.reject.integrity-without-official-integrity",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-empty-confirmed",
chatMetadata: {
integrity: "meta-ready-empty",
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "ready-empty",
});
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(result.loadState, "loading");
assert.equal(result.reason, "indexeddb-probe-pending");
assert.equal(live.writesBlocked, true);
assert.equal(harness.api.getCurrentGraph(), null);
assert.equal(
harness.api.readRuntimeDebugSnapshot().graphPersistence?.loadState,
"loading",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-empty-confirmed-passive",
chatMetadata: {
integrity: "meta-ready-empty-passive",
},
});
harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "ready-empty-passive",
});
harness.api.onMessageReceived();
assert.equal(
harness.runtimeContext.__contextImmediateSaveCalls,
0,
"空聊天的被动同步不应触发立即保存",
);
assert.equal(
harness.runtimeContext.__contextSaveCalls,
0,
"空聊天的被动同步不应触发防抖保存",
);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.st_bme_graph,
undefined,
"loading 状态下不能把空图被动写回 metadata",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-manager-unavailable-fallback",
globalChatId: "chat-manager-unavailable-fallback",
chatMetadata: {
integrity: "meta-manager-unavailable-fallback",
},
});
harness.runtimeContext.BmeChatManager = null;
const result = harness.api.loadGraphFromChat({
attemptIndex: harness.api.GRAPH_LOAD_RETRY_DELAYS_MS.length,
source: "manager-unavailable-fallback",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.loadState, "loading");
assert.equal(
harness.api.getGraphPersistenceState().loadState,
"blocked",
"IndexedDB manager 不可用时,重试耗尽后不应永久停留在 loading",
);
assert.equal(
harness.api.getGraphPersistenceState().reason,
"indexeddb-manager-unavailable",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-manager-unavailable-write",
globalChatId: "chat-manager-unavailable-write",
chatMetadata: {
integrity: "meta-manager-unavailable-write",
},
});
harness.runtimeContext.BmeChatManager = null;
const result = await harness.api.saveGraphToIndexedDb(
"chat-manager-unavailable-write",
createMeaningfulGraph("chat-manager-unavailable-write", "manager-unavailable-write"),
{
revision: 3,
reason: "manager-unavailable-write",
},
);
assert.equal(result.saved, false);
assert.equal(result.reason, "indexeddb-manager-unavailable");
assert.equal(
harness.api.getGraphPersistenceState().indexedDbLastError,
"indexeddb-manager-unavailable",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chatMetadata: {
integrity: "",
},
});
const graph = createMeaningfulGraph("chat-persist-fallback", "persist-fallback");
harness.api.setCurrentGraph(graph);
harness.api.setChatContext({
chatId: "",
chatMetadata: {},
characterId: "char-fallback",
groupId: null,
chat: [{ is_user: true, mes: "fallback chat id" }],
updateChatMetadata(patch) {
const base =
this.chatMetadata &&
typeof this.chatMetadata === "object" &&
!Array.isArray(this.chatMetadata)
? this.chatMetadata
: {};
this.chatMetadata = {
...base,
...(patch || {}),
};
},
saveMetadataDebounced() {},
});
const result = await harness.api.persistExtractionBatchResult({
reason: "persist-fallback-chat-id",
lastProcessedAssistantFloor: 6,
graphSnapshot: null,
persistDelta: null,
});
assert.equal(result.accepted, true);
assert.equal(
harness.api.getIndexedDbSnapshotForChat("chat-persist-fallback")?.meta?.chatId,
"chat-persist-fallback",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-read-failed-fallback",
globalChatId: "chat-indexeddb-read-failed-fallback",
chatMetadata: {
integrity: "meta-indexeddb-read-failed-fallback",
},
});
harness.runtimeContext.__indexedDbExportSnapshotShouldThrow = true;
const result = harness.api.loadGraphFromChat({
attemptIndex: harness.api.GRAPH_LOAD_RETRY_DELAYS_MS.length,
source: "indexeddb-read-failed-fallback",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.loadState, "loading");
assert.equal(
harness.api.getGraphPersistenceState().loadState,
"blocked",
"IndexedDB 读取失败时,重试耗尽后不应永久停留在 loading",
);
assert.equal(
harness.api.getGraphPersistenceState().reason,
"indexeddb-read-failed",
);
}
{
const commitMarker = buildGraphCommitMarker(
createMeaningfulGraph("chat-indexeddb-empty-mismatch-fallback", "marker"),
{
revision: 4,
storageTier: "indexeddb",
accepted: true,
reason: "test-empty-mismatch",
chatId: "chat-indexeddb-empty-mismatch-fallback",
integrity: "meta-indexeddb-empty-mismatch-fallback",
},
);
const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-empty-mismatch-fallback",
globalChatId: "chat-indexeddb-empty-mismatch-fallback",
chatMetadata: {
integrity: "meta-indexeddb-empty-mismatch-fallback",
[GRAPH_COMMIT_MARKER_KEY]: commitMarker,
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: harness.api.GRAPH_LOAD_RETRY_DELAYS_MS.length,
source: "indexeddb-empty-mismatch-fallback",
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(result.loadState, "loading");
assert.equal(
harness.api.getGraphPersistenceState().loadState,
"empty-confirmed",
"当 accepted commit marker 已成孤儿且本地不存在可恢复图谱源时,应自动降级为 empty-confirmed",
);
assert.match(
String(harness.api.getGraphPersistenceState().reason || ""),
/orphan-accepted-marker/,
);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.[GRAPH_COMMIT_MARKER_KEY],
null,
);
assert.equal(harness.runtimeContext.__contextImmediateSaveCalls, 1);
assert.equal(harness.api.getGraphPersistenceState().lastAcceptedRevision, 0);
assert.equal(harness.api.getGraphPersistenceState().commitMarker, null);
}
{
const commitMarker = buildGraphCommitMarker(
createMeaningfulGraph("chat-indexeddb-empty-chat-state-rescue", "marker"),
{
revision: 8,
storageTier: "indexeddb",
accepted: true,
reason: "test-chat-state-rescue",
chatId: "chat-indexeddb-empty-chat-state-rescue",
integrity: "meta-indexeddb-empty-chat-state-rescue",
},
);
const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-empty-chat-state-rescue",
globalChatId: "chat-indexeddb-empty-chat-state-rescue",
chatMetadata: {
integrity: "meta-indexeddb-empty-chat-state-rescue",
[GRAPH_COMMIT_MARKER_KEY]: commitMarker,
},
});
const sidecarGraph = stampPersistedGraph(
createMeaningfulGraph("chat-indexeddb-empty-chat-state-rescue", "sidecar"),
{
revision: 8,
integrity: "meta-indexeddb-empty-chat-state-rescue",
chatId: "chat-indexeddb-empty-chat-state-rescue",
reason: "sidecar-rescue-seed",
},
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
GRAPH_CHAT_STATE_NAMESPACE,
buildGraphChatStateSnapshot(sidecarGraph, {
revision: 8,
storageTier: "chat-state",
accepted: true,
reason: "sidecar-rescue-seed",
chatId: "chat-indexeddb-empty-chat-state-rescue",
integrity: "meta-indexeddb-empty-chat-state-rescue",
lastProcessedAssistantFloor: 6,
extractionCount: 3,
}),
);
const result = await harness.api.loadGraphFromIndexedDb(
"chat-indexeddb-empty-chat-state-rescue",
{
source: "indexeddb-empty-chat-state-rescue",
attemptIndex: 0,
allowOverride: true,
applyEmptyState: true,
},
);
assert.equal(result.loaded, true);
assert.equal(result.loadState, "loaded");
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-sidecar",
);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.[GRAPH_COMMIT_MARKER_KEY]
?.revision,
8,
);
assert.equal(harness.runtimeContext.__contextImmediateSaveCalls, 0);
assert.equal(
harness.api.getGraphPersistenceState().persistMismatchReason,
"persist-mismatch:indexeddb-behind-commit-marker",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-create-first-graph",
chatMetadata: {
integrity: "integrity-before-first-save",
},
});
harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "ready-for-first-save",
});
harness.api.setCurrentGraph(
createMeaningfulGraph("chat-create-first-graph", "first-save"),
);
const result = harness.api.saveGraphToChat({
reason: "first-meaningful-graph",
});
assert.equal(result.saved, false);
assert.equal(result.queued, true);
assert.equal(result.saveMode, "indexeddb-queued");
assert.equal(harness.runtimeContext.__contextImmediateSaveCalls, 0);
assert.equal(harness.runtimeContext.__contextSaveCalls, 0);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.integrity ===
"integrity-before-first-save",
true,
"插件保存图谱时不能改写宿主 metadata.integrity",
);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.st_bme_graph,
undefined,
);
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision) > 0,
true,
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-promote",
chatMetadata: undefined,
sessionStore: sharedSession,
});
writer.api.writeGraphShadowSnapshot(
"chat-promote",
createMeaningfulGraph("chat-promote", "promote"),
{ revision: 9, reason: "pre-refresh" },
);
const reader = await createGraphPersistenceHarness({
chatId: "chat-promote",
chatMetadata: {
integrity: "meta-ready-promote",
},
sessionStore: sharedSession,
});
const result = reader.api.loadGraphFromChat({
attemptIndex: 0,
source: "promote-when-metadata-ready",
});
const live = reader.api.getGraphPersistenceLiveState();
assert.equal(result.loadState, "shadow-restored");
assert.equal(
reader.runtimeContext.__chatContext.chatMetadata?.st_bme_graph?.nodes
?.length,
undefined,
);
assert.equal(
reader.runtimeContext.__chatContext.chatMetadata?.integrity,
"meta-ready-promote",
);
assert.equal(reader.runtimeContext.__contextImmediateSaveCalls, 0);
assert.equal(reader.runtimeContext.__contextSaveCalls, 0);
assert.equal(live.lastPersistedRevision, 9);
assert.equal(live.pendingPersist, true);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-decouple",
chatMetadata: {
integrity: "meta-decouple",
},
});
const runtimeGraph = createMeaningfulGraph("chat-decouple", "runtime");
harness.api.setCurrentGraph(runtimeGraph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-decouple",
revision: 3,
lastPersistedRevision: 0,
writesBlocked: false,
});
const result = harness.api.saveGraphToChat({
reason: "decouple-metadata-runtime",
markMutation: false,
persistMetadata: true,
});
assert.equal(result.saved, true);
const persistedGraph =
harness.runtimeContext.__chatContext.chatMetadata?.st_bme_graph;
assert.notEqual(
persistedGraph,
harness.api.getCurrentGraph(),
"写入 metadata 时必须使用独立 graph 快照",
);
persistedGraph.nodes[0].fields.title = "metadata-mutated";
assert.equal(
harness.api.getCurrentGraph().nodes[0].fields.title,
"事件-runtime",
"metadata 修改不能反向污染运行时 graph",
);
harness.api.getCurrentGraph().nodes[0].fields.title = "runtime-mutated";
assert.equal(
persistedGraph.nodes[0].fields.title,
"metadata-mutated",
"运行时修改不能反向污染已保存 metadata",
);
}
{
const officialGraph = stampPersistedGraph(
createMeaningfulGraph("chat-load-official", "official"),
{
revision: 4,
integrity: "meta-load-official",
chatId: "chat-load-official",
reason: "official-save",
},
);
const harness = await createGraphPersistenceHarness({
chatId: "chat-load-official",
chatMetadata: {
integrity: "meta-load-official",
st_bme_graph: officialGraph,
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "load-official-decoupled",
});
assert.equal(result.loadState, "loading");
const runtimeGraph = harness.api.getCurrentGraph();
const persistedGraph =
harness.runtimeContext.__chatContext.chatMetadata.st_bme_graph;
assert.notEqual(
runtimeGraph,
persistedGraph,
"从 official metadata 恢复到运行时必须使用独立对象",
);
runtimeGraph.nodes[0].fields.title = "runtime-after-load";
assert.equal(
persistedGraph.nodes[0].fields.title,
"事件-official",
"official metadata 不应被运行时修改污染",
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-load-shadow",
chatMetadata: {
integrity: "meta-load-shadow",
st_bme_graph: stampPersistedGraph(
createMeaningfulGraph("chat-load-shadow", "official-older"),
{
revision: 2,
integrity: "meta-load-shadow",
chatId: "chat-load-shadow",
reason: "official-older",
},
),
},
sessionStore: sharedSession,
});
writer.api.writeGraphShadowSnapshot(
"chat-load-shadow",
createMeaningfulGraph("chat-load-shadow", "shadow"),
{
revision: 5,
reason: "shadow-newer",
},
);
const reader = await createGraphPersistenceHarness({
chatId: "chat-load-shadow",
chatMetadata: {
integrity: "meta-load-shadow",
st_bme_graph: stampPersistedGraph(
createMeaningfulGraph("chat-load-shadow", "official-older"),
{
revision: 2,
integrity: "meta-load-shadow",
chatId: "chat-load-shadow",
reason: "official-older",
},
),
},
sessionStore: sharedSession,
});
const result = reader.api.loadGraphFromChat({
attemptIndex: 0,
source: "load-shadow-decoupled",
});
assert.equal(result.loadState, "shadow-restored");
const runtimeGraph = reader.api.getCurrentGraph();
const persistedGraph =
reader.runtimeContext.__chatContext.chatMetadata.st_bme_graph;
assert.notEqual(
runtimeGraph,
persistedGraph,
"从 shadow snapshot 提升后,运行时与 metadata 也必须解耦",
);
runtimeGraph.nodes[0].fields.title = "runtime-shadow-mutated";
assert.equal(
runtimeGraph.nodes[0].fields.title,
"runtime-shadow-mutated",
);
assert.equal(
persistedGraph.nodes[0].fields.title,
"事件-official-older",
"metadata 兼容加载后的运行时修改不能污染已保存 metadata",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-two-saves",
chatMetadata: {
integrity: "meta-two-saves",
},
});
harness.api.setCurrentGraph(createMeaningfulGraph("chat-two-saves", "first"));
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-two-saves",
revision: 1,
lastPersistedRevision: 0,
writesBlocked: false,
});
const firstSave = harness.api.saveGraphToChat({
reason: "first-save",
markMutation: false,
persistMetadata: true,
});
assert.equal(firstSave.saved, true);
const firstPersistedGraph =
harness.runtimeContext.__chatContext.chatMetadata.st_bme_graph;
harness.api.getCurrentGraph().nodes[0].fields.title = "runtime-between-saves";
assert.equal(
firstPersistedGraph.nodes[0].fields.title,
"事件-first",
"第一次保存后的 metadata 不应被后续运行时修改污染",
);
harness.api.setGraphPersistenceState({ revision: 2 });
const secondSave = harness.api.saveGraphToChat({
reason: "second-save",
markMutation: false,
persistMetadata: true,
});
assert.equal(secondSave.saved, true);
const secondPersistedGraph =
harness.runtimeContext.__chatContext.chatMetadata.st_bme_graph;
assert.notEqual(
secondPersistedGraph,
firstPersistedGraph,
"第二次保存应生成新的 metadata graph 快照",
);
assert.equal(
secondPersistedGraph.nodes[0].fields.title,
"runtime-between-saves",
"第二次保存应反映第二轮运行时修改",
);
harness.api.getCurrentGraph().nodes[0].fields.title =
"runtime-after-second-save";
assert.equal(
firstPersistedGraph.nodes[0].fields.title,
"事件-first",
"第二轮运行时修改仍不能污染第一次已保存 metadata",
);
assert.equal(
secondPersistedGraph.nodes[0].fields.title,
"runtime-between-saves",
"第二次已保存 metadata 也不能被后续运行时修改污染",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-idb-ancillary-warning",
globalChatId: "chat-idb-ancillary-warning",
chatMetadata: {
integrity: "meta-idb-ancillary-warning",
},
});
harness.api.setCurrentGraph(
createMeaningfulGraph("chat-idb-ancillary-warning", "ancillary-warning"),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-idb-ancillary-warning",
revision: 7,
lastPersistedRevision: 0,
writesBlocked: false,
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
nativeRolloutVersion: 1,
persistUseNativeDelta: false,
};
harness.runtimeContext.__scheduleUploadShouldThrow = true;
const result = await harness.api.saveGraphToIndexedDb(
"chat-idb-ancillary-warning",
harness.api.getCurrentGraph(),
{
revision: 7,
reason: "ancillary-warning-save",
},
);
assert.equal(result.saved, true);
assert.match(String(result.warning || ""), /schedule-upload-failed/);
assert.equal(
harness.api.getIndexedDbSnapshot().meta.revision,
7,
"附属步骤失败时IndexedDB 主写仍应视为成功",
);
const persistDeltaDiagnostics = harness.api.getGraphPersistenceState().persistDelta;
assert.equal(Boolean(persistDeltaDiagnostics), true);
assert.equal(persistDeltaDiagnostics.status, "committed");
assert.equal(persistDeltaDiagnostics.path, "js");
assert.equal(persistDeltaDiagnostics.requestedNative, false);
assert.equal(Number.isFinite(Number(persistDeltaDiagnostics.buildMs)), true);
assert.equal(Number.isFinite(Number(persistDeltaDiagnostics.prepareMs)), true);
assert.equal(Number.isFinite(Number(persistDeltaDiagnostics.lookupMs)), true);
assert.equal(Number.isFinite(Number(persistDeltaDiagnostics.jsDiffMs)), true);
assert.equal(
Number(persistDeltaDiagnostics.serializationCacheHits || 0) +
Number(persistDeltaDiagnostics.serializationCacheMisses || 0) >
0,
true,
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-idb-single-snapshot-build",
globalChatId: "chat-idb-single-snapshot-build",
chatMetadata: {
integrity: "meta-idb-single-snapshot-build",
},
});
harness.api.setCurrentGraph(
createMeaningfulGraph("chat-idb-single-snapshot-build", "single-snapshot-build"),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-idb-single-snapshot-build",
revision: 8,
lastPersistedRevision: 0,
writesBlocked: false,
});
const originalBuildSnapshotFromGraph = harness.runtimeContext.buildSnapshotFromGraph;
let buildSnapshotCallCount = 0;
harness.runtimeContext.buildSnapshotFromGraph = (...args) => {
buildSnapshotCallCount += 1;
return originalBuildSnapshotFromGraph(...args);
};
const result = await harness.api.saveGraphToIndexedDb(
"chat-idb-single-snapshot-build",
harness.api.getCurrentGraph(),
{
revision: 8,
reason: "single-snapshot-build-save",
scheduleCloudUpload: false,
},
);
assert.equal(result.saved, true);
assert.equal(
buildSnapshotCallCount,
1,
"saveGraphToIndexedDb 热路径应复用首次构建的 snapshot而不是提交后再重建一次",
);
assert.equal(result.snapshot?.meta?.revision, 8);
assert.equal(
harness.api.getIndexedDbSnapshot()?.meta?.revision,
8,
"复用首次 snapshot 后仍应正确回填缓存 revision",
);
}
{
const chatId = "chat-idb-direct-delta-prebuilt-persist-snapshot";
const baseGraph = createMeaningfulGraph(chatId, "direct-delta-base");
const runtimeGraph = createMeaningfulGraph(chatId, "direct-delta-after");
const baseSnapshot = buildSnapshotFromGraph(baseGraph, {
chatId,
revision: 7,
});
const persistSnapshot = buildSnapshotFromGraph(runtimeGraph, {
chatId,
revision: 8,
baseSnapshot,
});
const directDelta = buildPersistDelta(baseSnapshot, persistSnapshot, {
useNativeDelta: false,
});
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
chatMetadata: {
integrity: "meta-idb-direct-delta-prebuilt-persist-snapshot",
},
indexedDbSnapshot: baseSnapshot,
});
harness.api.setCurrentGraph(runtimeGraph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId,
revision: 8,
lastPersistedRevision: 0,
writesBlocked: false,
});
const originalBuildSnapshotFromGraph = harness.runtimeContext.buildSnapshotFromGraph;
let buildSnapshotCallCount = 0;
harness.runtimeContext.buildSnapshotFromGraph = (...args) => {
buildSnapshotCallCount += 1;
return originalBuildSnapshotFromGraph(...args);
};
const result = await harness.api.saveGraphToIndexedDb(chatId, runtimeGraph, {
revision: 8,
reason: "direct-delta-prebuilt-persist-snapshot-save",
scheduleCloudUpload: false,
persistDelta: directDelta,
persistSnapshot,
});
assert.equal(result.saved, true);
assert.equal(
buildSnapshotCallCount,
0,
"direct-delta 且已提供 persistSnapshot 时不应再次构建 snapshot",
);
assert.equal(result.snapshot?.meta?.revision, 8);
assert.equal(harness.api.getIndexedDbSnapshot()?.meta?.revision, 8);
}
{
const chatId = "chat-idb-dirty-runtime-fast-path";
const baseGraph = createMeaningfulGraph(chatId, "dirty-runtime-base");
const runtimeGraph = cloneGraphForPersistence(baseGraph, chatId);
updateNode(runtimeGraph, runtimeGraph.nodes[0]?.id, {
importance: Number(runtimeGraph.nodes[0]?.importance || 0) + 2,
});
const baseSnapshot = buildSnapshotFromGraph(baseGraph, {
chatId,
revision: 7,
});
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
chatMetadata: {
integrity: "meta-idb-dirty-runtime-fast-path",
},
indexedDbSnapshot: baseSnapshot,
});
harness.api.setCurrentGraph(runtimeGraph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId,
revision: 8,
lastPersistedRevision: 0,
writesBlocked: false,
});
const originalBuildSnapshotFromGraph = harness.runtimeContext.buildSnapshotFromGraph;
let buildSnapshotCallCount = 0;
harness.runtimeContext.buildSnapshotFromGraph = (...args) => {
buildSnapshotCallCount += 1;
return originalBuildSnapshotFromGraph(...args);
};
const result = await harness.api.saveGraphToIndexedDb(chatId, runtimeGraph, {
revision: 8,
reason: "dirty-runtime-fast-path-save",
scheduleCloudUpload: false,
sourceGraph: runtimeGraph,
});
assert.equal(result.saved, true);
assert.equal(
buildSnapshotCallCount,
0,
"dirty-set 命中时 saveGraphToIndexedDb 不应退回 full snapshot build",
);
assert.equal(result.snapshot?.meta?.revision, 8);
assert.equal(harness.api.getIndexedDbSnapshot()?.meta?.revision, 8);
}
{
const chatId = "chat-indexeddb-probe-empty-early-return";
const persistedSnapshot = {
meta: { revision: 0, chatId },
nodes: [],
edges: [],
tombstones: [],
state: {
lastProcessedFloor: -1,
extractionCount: 0,
},
};
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
chatMetadata: {
integrity: "meta-indexeddb-probe-empty-early-return",
},
indexedDbSnapshot: persistedSnapshot,
});
harness.runtimeContext.__globalChatId = chatId;
harness.runtimeContext.__chatContext.chatId = chatId;
harness.api.setChatContext({
...harness.api.getChatContext(),
chatId,
chatMetadata: {
integrity: "meta-indexeddb-probe-empty-early-return",
},
});
harness.api.setCurrentGraph(
createMeaningfulGraph(chatId, "probe-empty-runtime-current"),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId,
revision: 1,
lastPersistedRevision: 1,
storagePrimary: "indexeddb",
storageMode: "indexeddb",
writesBlocked: false,
});
const originalCreateDb = harness.runtimeContext.BmeChatManager.prototype._createDb;
let exportSnapshotCalls = 0;
let exportProbeCalls = 0;
harness.runtimeContext.BmeChatManager.prototype._createDb = function(dbChatId = "") {
const baseDb = originalCreateDb.call(this, dbChatId);
return {
...baseDb,
async exportSnapshot() {
exportSnapshotCalls += 1;
return await baseDb.exportSnapshot();
},
async exportSnapshotProbe() {
exportProbeCalls += 1;
const snapshot = harness.api.getIndexedDbSnapshotForChat(dbChatId) || {
meta: { revision: 0, chatId: String(dbChatId || "") },
state: { lastProcessedFloor: -1, extractionCount: 0 },
nodes: [],
edges: [],
tombstones: [],
};
return {
meta: {
...(snapshot.meta || {}),
chatId: String(dbChatId || ""),
revision: Number(snapshot?.meta?.revision || 0),
nodeCount: Array.isArray(snapshot?.nodes) ? snapshot.nodes.length : 0,
edgeCount: Array.isArray(snapshot?.edges) ? snapshot.edges.length : 0,
tombstoneCount: Array.isArray(snapshot?.tombstones)
? snapshot.tombstones.length
: 0,
},
state: {
lastProcessedFloor: Number(snapshot?.state?.lastProcessedFloor ?? -1),
extractionCount: Number(snapshot?.state?.extractionCount ?? 0),
},
nodes: [],
edges: [],
tombstones: [],
__stBmeProbeOnly: true,
__stBmeTombstonesOmitted: true,
};
},
};
};
const result = await harness.api.loadGraphFromIndexedDb(chatId, {
source: "probe-empty-early-return",
attemptIndex: 0,
});
assert.equal(result.loaded, false);
assert.equal(exportProbeCalls, 1);
assert.equal(
exportSnapshotCalls,
0,
"empty/probe 早退应在 probe 阶段终止,而不是继续全量导出 snapshot",
);
harness.runtimeContext.BmeChatManager.prototype._createDb = originalCreateDb;
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-pending-persist-retry",
globalChatId: "chat-pending-persist-retry",
chatMetadata: {
integrity: "meta-pending-persist-retry",
},
chat: [
{ is_user: true, mes: "用户发言" },
{ is_user: false, mes: "助手回复" },
],
});
const graph = createMeaningfulGraph(
"chat-pending-persist-retry",
"pending-persist-retry",
);
graph.historyState.lastProcessedAssistantFloor = -1;
graph.lastProcessedSeq = -1;
graph.historyState.lastBatchStatus = {
processedRange: [1, 1],
completed: true,
stages: {
core: { outcome: "success" },
finalize: { outcome: "success" },
},
persistence: {
outcome: "queued",
accepted: false,
storageTier: "none",
reason: "extraction-batch-complete:pending",
revision: 7,
saveMode: "immediate",
saved: false,
queued: true,
blocked: true,
},
historyAdvanceAllowed: false,
historyAdvanced: false,
};
const committedGraph = structuredClone(graph);
committedGraph.historyState.lastProcessedAssistantFloor = 1;
committedGraph.lastProcessedSeq = 1;
committedGraph.batchJournal = [
{
id: "journal-queued-1",
processedRange: [1, 1],
createdAt: Date.now(),
},
];
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-pending-persist-retry",
revision: 7,
lastPersistedRevision: 0,
queuedPersistRevision: 7,
queuedPersistChatId: "chat-pending-persist-retry",
queuedPersistMode: "immediate",
pendingPersist: true,
writesBlocked: false,
});
harness.api.writeGraphShadowSnapshot(
"chat-pending-persist-retry",
committedGraph,
{
revision: 7,
reason: "queued-persist-authoritative",
},
);
harness.runtimeContext.__markSyncDirtyShouldThrow = true;
const result = await harness.api.retryPendingGraphPersist({
reason: "queued-persist-retry-test",
});
assert.equal(result.accepted, true);
assert.equal(
harness.api.getGraphPersistenceState().pendingPersist,
false,
"pendingPersist 在补存成功后应被清除",
);
assert.equal(
harness.api.getCurrentGraph().historyState.lastProcessedAssistantFloor,
1,
"补存成功后应推进 lastProcessedAssistantFloor",
);
assert.equal(
harness.api.getCurrentGraph().historyState.lastBatchStatus.historyAdvanceAllowed,
true,
);
assert.equal(
harness.api.getCurrentGraph().historyState.lastBatchStatus.persistence.outcome,
"saved",
);
assert.equal(
harness.api.getCurrentGraph().batchJournal?.length,
1,
"pending persist retry 应把 authoritative batch journal 回填到 runtime graph",
);
assert.equal(
harness.api.getCurrentGraph().batchJournal?.[0]?.id,
"journal-queued-1",
);
}
{
const graph = createMeaningfulGraph(
"chat-load-legacy-pending-repair",
"load-legacy-pending-repair",
);
graph.historyState.lastProcessedAssistantFloor = 1;
graph.lastProcessedSeq = 1;
graph.historyState.lastBatchStatus = {
processedRange: [1, 1],
completed: true,
persistence: {
outcome: "queued",
accepted: false,
storageTier: "metadata-full",
reason: "old-version-pending",
revision: 4,
saveMode: "immediate",
saved: false,
queued: true,
blocked: true,
},
historyAdvanceAllowed: false,
historyAdvanced: false,
};
stampPersistedGraph(graph, {
revision: 4,
chatId: "chat-load-legacy-pending-repair",
reason: "legacy-pending-repair-seed",
});
const snapshot = buildSnapshotFromGraph(graph, {
chatId: "meta-load-legacy-pending-repair",
revision: 4,
meta: {
integrity: "meta-load-legacy-pending-repair",
},
});
const harness = await createGraphPersistenceHarness({
chatId: "chat-load-legacy-pending-repair",
globalChatId: "chat-load-legacy-pending-repair",
chatMetadata: {
integrity: "meta-load-legacy-pending-repair",
[GRAPH_COMMIT_MARKER_KEY]: {
accepted: true,
revision: 4,
storageTier: "indexeddb",
chatId: "meta-load-legacy-pending-repair",
},
},
indexedDbSnapshots: {
"meta-load-legacy-pending-repair": snapshot,
},
chat: [
{ is_user: true, mes: "旧聊天" },
{ is_user: false, mes: "旧回复" },
],
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
graphLocalStorageMode: "indexeddb",
};
const result = await harness.api.loadGraphFromIndexedDb(
"meta-load-legacy-pending-repair",
{
source: "legacy-pending-load-repair-test",
allowOverride: true,
},
);
assert.equal(result.loaded, true, result.reason);
const repairedStatus = harness.api.getCurrentGraph().historyState.lastBatchStatus;
assert.equal(repairedStatus.historyAdvanceAllowed, true);
assert.equal(repairedStatus.persistence.accepted, true);
assert.equal(repairedStatus.persistence.saved, true);
assert.equal(repairedStatus.persistence.queued, false);
assert.equal(repairedStatus.persistence.blocked, false);
assert.equal(repairedStatus.persistence.storageTier, "indexeddb");
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
harness.api.getIndexedDbSnapshotForChat("meta-load-legacy-pending-repair")?.meta?.lastMutationReason,
"legacy-persistence-auto-repair-after-load",
"加载旧 pending 状态后应将归一化结果写回 canonical store",
);
}
{
const graph = createMeaningfulGraph(
"chat-load-legacy-pending-no-proof",
"load-legacy-pending-no-proof",
);
graph.historyState.lastProcessedAssistantFloor = 1;
graph.lastProcessedSeq = 1;
graph.historyState.lastBatchStatus = {
processedRange: [1, 1],
completed: true,
persistence: {
outcome: "queued",
accepted: false,
storageTier: "metadata-full",
reason: "old-version-pending",
revision: 4,
saveMode: "immediate",
saved: false,
queued: true,
blocked: true,
},
historyAdvanceAllowed: false,
historyAdvanced: false,
};
stampPersistedGraph(graph, {
revision: 4,
chatId: "chat-load-legacy-pending-no-proof",
reason: "legacy-pending-no-proof-seed",
});
const snapshot = buildSnapshotFromGraph(graph, {
chatId: "meta-load-legacy-pending-no-proof",
revision: 4,
meta: {
integrity: "meta-load-legacy-pending-no-proof",
},
});
const harness = await createGraphPersistenceHarness({
chatId: "chat-load-legacy-pending-no-proof",
globalChatId: "chat-load-legacy-pending-no-proof",
chatMetadata: {
integrity: "meta-load-legacy-pending-no-proof",
},
indexedDbSnapshots: {
"meta-load-legacy-pending-no-proof": snapshot,
},
chat: [
{ is_user: true, mes: "旧聊天" },
{ is_user: false, mes: "旧回复" },
],
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
graphLocalStorageMode: "indexeddb",
};
const result = await harness.api.loadGraphFromIndexedDb(
"meta-load-legacy-pending-no-proof",
{
source: "legacy-pending-load-no-proof-test",
allowOverride: true,
},
);
assert.equal(result.loaded, true, result.reason);
const unrepairedStatus = harness.api.getCurrentGraph().historyState.lastBatchStatus;
assert.equal(
unrepairedStatus.historyAdvanceAllowed,
false,
"无独立 accepted 证据时,加载旧 pending 状态不得自动放行历史推进",
);
assert.equal(unrepairedStatus.persistence.accepted, false);
assert.equal(unrepairedStatus.persistence.queued, true);
assert.equal(unrepairedStatus.persistence.blocked, true);
}
{
const graph = createMeaningfulGraph(
"chat-runtime-fallback-vector-maintenance",
"runtime-fallback-vector-maintenance",
);
graph.historyState.chatId = "chat-runtime-fallback-vector-maintenance";
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chat: [
{ is_user: true, mes: "已有聊天" },
{ is_user: false, mes: "已有回复" },
],
});
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: GRAPH_LOAD_STATES.NO_CHAT,
chatId: "chat-runtime-fallback-vector-maintenance",
dbReady: false,
writesBlocked: true,
});
assert.equal(
harness.api.ensureGraphMutationReady("重建向量", {
notify: false,
allowRuntimeGraphFallback: true,
}),
true,
"live chat id 暂空但 runtime graph 已明确绑定聊天时,向量维护不应被误判为未进入聊天",
);
const status = harness.api.getPanelRuntimeStatus();
assert.equal(status.text, "图谱已加载");
assert.match(status.meta, /维护操作会使用图谱身份继续/);
}
{
const graph = createMeaningfulGraph(
"chat-runtime-fallback-identity-repair",
"runtime-fallback-identity-repair",
);
graph.historyState.chatId = "";
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chat: [
{ is_user: true, mes: "已有聊天" },
{ is_user: false, mes: "已有回复" },
],
});
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: GRAPH_LOAD_STATES.NO_CHAT,
chatId: "chat-runtime-fallback-identity-repair",
dbReady: false,
writesBlocked: true,
});
assert.equal(
harness.api.ensureGraphMutationReady("重建向量", {
notify: false,
allowRuntimeGraphFallback: true,
}),
true,
"面板/持久化状态已有聊天 ID 且图谱自身缺身份时,向量维护前应补齐缺失身份",
);
assert.equal(
harness.api.getCurrentGraph().historyState.chatId,
"chat-runtime-fallback-identity-repair",
);
}
{
const graph = createMeaningfulGraph(
"chat-runtime-fallback-denied",
"runtime-fallback-denied",
);
graph.historyState.chatId = "chat-runtime-fallback-denied";
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chat: [{ is_user: true, mes: "其它上下文" }],
});
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: GRAPH_LOAD_STATES.NO_CHAT,
chatId: "",
dbReady: false,
writesBlocked: true,
});
assert.equal(
harness.api.ensureGraphMutationReady("重建向量", {
notify: false,
allowRuntimeGraphFallback: true,
}),
false,
"没有 graphPersistenceState.chatId 强绑定时,不应仅凭 runtimeGraph/chat 内容放开写入",
);
}
{
const graph = createMeaningfulGraph(
"chat-runtime-fallback-conflict",
"runtime-fallback-conflict",
);
graph.historyState.chatId = "";
const harness = await createGraphPersistenceHarness({
chatId: "",
globalChatId: "",
chat: [{ is_user: true, mes: "已有聊天" }],
});
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: GRAPH_LOAD_STATES.NO_CHAT,
chatId: "chat-runtime-fallback-conflict",
commitMarker: { chatId: "other-chat" },
dbReady: false,
writesBlocked: true,
});
assert.equal(
harness.api.ensureGraphMutationReady("重建向量", {
notify: false,
allowRuntimeGraphFallback: true,
}),
false,
"commit marker 指向其它聊天时,不应补齐身份或放开向量写入",
);
assert.equal(harness.api.getCurrentGraph().historyState.chatId, "");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-pending-persist-already-accepted",
globalChatId: "chat-pending-persist-already-accepted",
chatMetadata: {
integrity: "meta-pending-persist-already-accepted",
},
chat: [
{ is_user: true, mes: "用户发言" },
{ is_user: false, mes: "助手回复" },
],
});
const graph = createMeaningfulGraph(
"chat-pending-persist-already-accepted",
"pending-persist-already-accepted",
);
graph.historyState.lastProcessedAssistantFloor = 1;
graph.lastProcessedSeq = 1;
graph.historyState.lastBatchStatus = {
processedRange: [1, 1],
completed: true,
persistence: {
outcome: "queued",
accepted: false,
storageTier: "authority-sql",
reason: "extraction-batch-complete:pending",
revision: 7,
saveMode: "immediate",
saved: false,
queued: true,
blocked: true,
},
historyAdvanceAllowed: false,
historyAdvanced: false,
};
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-pending-persist-already-accepted",
revision: 7,
lastPersistedRevision: 7,
lastAcceptedRevision: 7,
acceptedStorageTier: "authority-sql",
queuedPersistRevision: 7,
queuedPersistChatId: "chat-pending-persist-already-accepted",
queuedPersistMode: "immediate",
pendingPersist: true,
writesBlocked: false,
});
harness.runtimeContext.__markSyncDirtyShouldThrow = true;
const result = await harness.api.retryPendingGraphPersist({
reason: "queued-persist-already-accepted-test",
});
assert.equal(result.accepted, true);
assert.equal(
harness.api.getGraphPersistenceState().pendingPersist,
false,
"已被 lastAcceptedRevision 覆盖的 pendingPersist 应在重试时直接清除",
);
assert.equal(
harness.api.getCurrentGraph().historyState.lastBatchStatus.persistence.accepted,
true,
);
assert.equal(
harness.api.getCurrentGraph().historyState.lastBatchStatus.historyAdvanceAllowed,
true,
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-pending-current",
globalChatId: "chat-pending-current",
chatMetadata: {
integrity: "meta-pending-current",
},
chat: [
{ is_user: true, mes: "当前聊天用户发言" },
{ is_user: false, mes: "当前聊天助手回复" },
],
});
const graph = createMeaningfulGraph(
"chat-pending-current",
"pending-persist-chat-mismatch",
);
graph.historyState.lastBatchStatus = {
processedRange: [1, 1],
completed: true,
persistence: {
outcome: "queued",
accepted: false,
storageTier: "authority-sql",
reason: "extraction-batch-complete:pending",
revision: 7,
saveMode: "immediate",
saved: false,
queued: true,
blocked: true,
},
historyAdvanceAllowed: false,
historyAdvanced: false,
};
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-pending-current",
revision: 9,
lastPersistedRevision: 9,
lastAcceptedRevision: 9,
acceptedStorageTier: "authority-sql",
queuedPersistRevision: 7,
queuedPersistChatId: "other-chat-pending",
queuedPersistMode: "immediate",
pendingPersist: true,
writesBlocked: false,
});
const result = await harness.api.retryPendingGraphPersist({
reason: "queued-persist-chat-mismatch-test",
});
assert.equal(result.accepted, false);
assert.equal(result.reason, "queued-chat-mismatch");
assert.equal(
harness.api.getGraphPersistenceState().pendingPersist,
true,
"其它聊天的 queued pending 不能被当前聊天 accepted revision 清掉",
);
}
{
const chatId = "meta-authority-indexeddb-migration";
const legacyGraph = stampPersistedGraph(
createMeaningfulGraph(chatId, "authority-indexeddb-migration"),
{
revision: 4,
integrity: "meta-authority-indexeddb-migration",
chatId,
reason: "legacy-indexeddb",
},
);
const legacySnapshot = buildSnapshotFromGraph(legacyGraph, {
chatId,
revision: 4,
meta: {
integrity: "meta-authority-indexeddb-migration",
storagePrimary: "indexeddb",
storageMode: "indexeddb",
syncDirty: true,
},
});
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
chatMetadata: {
integrity: "meta-authority-indexeddb-migration",
},
indexedDbSnapshot: legacySnapshot,
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
authorityEnabled: "on",
authorityPrimaryWhenAvailable: true,
authorityStorageMode: "server-primary",
authoritySqlPrimary: true,
authorityBrowserCacheMode: "minimal",
};
harness.api.setAuthorityCapabilityState({
installed: true,
healthy: true,
sessionReady: true,
permissionReady: true,
features: ["sql.query", "sql.mutation", "trivium.search", "jobs", "blob"],
jobs: {
builtinTypes: ["delay", "sql.backup", "trivium.flush", "fs.import-jsonl"],
registry: {
jobTypes: ["delay", "sql.backup", "trivium.flush", "fs.import-jsonl"],
},
},
reason: "ok",
lastProbeAt: Date.now(),
});
const result = await harness.api.loadGraphFromIndexedDb(chatId, {
source: "authority-indexeddb-migration",
attemptIndex: 0,
});
assert.equal(result.loaded, true);
assert.equal(result.reason, "authority-sql:authority-indexeddb-migration");
assert.equal(harness.runtimeContext.__syncNowCalls.length, 0);
const authoritySnapshot = harness.api.getAuthoritySnapshotForChat(chatId);
assert.equal(authoritySnapshot?.nodes?.length, 1);
assert.equal(authoritySnapshot?.meta?.storagePrimary, AUTHORITY_GRAPH_STORE_KIND);
assert.equal(authoritySnapshot?.meta?.storageMode, AUTHORITY_GRAPH_STORE_MODE);
assert.equal(
authoritySnapshot?.meta?.migrationSource,
"legacy_indexeddb_to_authority",
);
assert.equal(authoritySnapshot?.meta?.syncDirty, false);
const safetySnapshot = harness.api.getIndexedDbSnapshotForChat(
harness.runtimeContext.buildRestoreSafetyChatId(chatId),
);
assert.equal(safetySnapshot?.nodes?.length, 1);
assert.equal(safetySnapshot?.meta?.restoreSafetySnapshotExists, true);
assert.equal(safetySnapshot?.meta?.restoreSafetySnapshotChatId, chatId);
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(live.storagePrimary, AUTHORITY_GRAPH_STORE_KIND);
assert.equal(live.storageMode, AUTHORITY_GRAPH_STORE_MODE);
assert.equal(live.authorityMigrationState, "completed");
assert.equal(live.authorityMigrationSource, "legacy_indexeddb_to_authority");
assert.equal(Number(live.authorityMigrationRevision), 4);
assert.equal(
live.lastAuthorityMigrationResult?.safetySnapshotResult?.restoreSafetyCaptured,
true,
);
assert.equal(
harness.runtimeContext.__indexedDbSnapshots.has(chatId),
true,
"迁移成功后仍保留 legacy IndexedDB 源数据,不删除本地数据",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-authority-empty-jobs",
globalChatId: "chat-authority-empty-jobs",
});
harness.api.setAuthorityCapabilityState({
installed: true,
healthy: true,
sessionReady: true,
permissionReady: true,
features: ["sql.query", "sql.mutation", "trivium.search", "jobs", "blob"],
supportedJobTypes: [],
supportedJobTypesKnown: true,
reason: "ok",
});
assert.equal(
harness.api.shouldUseAuthorityJobs({ mode: "authority", source: "authority-trivium" }),
false,
"显式空 Authority job 白名单应阻止 vector rebuild job 提交",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-authority-sql-storage-only",
globalChatId: "chat-authority-sql-storage-only",
});
harness.runtimeContext.extension_settings[MODULE_NAME] = {
authorityEnabled: "on",
authorityPrimaryWhenAvailable: true,
authorityStorageMode: "server-primary",
authoritySqlPrimary: true,
};
const capability = harness.api.setAuthorityCapabilityState({
installed: true,
healthy: true,
sessionReady: true,
permissionReady: true,
features: ["sql.query", "sql.mutation"],
reason: "missing-required-features",
lastProbeAt: Date.now(),
});
assert.equal(
capability.serverPrimaryReady,
false,
"缺少 jobs/blob/trivium 时整体 Authority server-primary 应保持降级显示",
);
assert.equal(
capability.storagePrimaryReady,
true,
"SQL 存储能力已就绪时图谱主存储应可用",
);
assert.equal(
harness.api.shouldUseAuthorityGraphStore(
harness.runtimeContext.extension_settings[MODULE_NAME],
capability,
),
true,
"Authority SQL 图谱主存储不应被 jobs/blob/trivium 附属能力误伤",
);
assert.equal(
harness.api.shouldUseAuthorityJobs({ mode: "authority", source: "authority-trivium" }),
false,
"jobs 不可用时 Authority job 提交仍应被禁用",
);
harness.api.setCurrentGraph(
stampPersistedGraph(
createMeaningfulGraph("chat-authority-sql-storage-only", "authority-sql-storage-only"),
{
revision: 6,
integrity: "chat-authority-sql-storage-only",
chatId: "chat-authority-sql-storage-only",
reason: "authority-sql-storage-only-seed",
},
),
);
const persistResult = await harness.api.persistExtractionBatchResult({
reason: "authority-sql-storage-only-persist",
lastProcessedAssistantFloor: 6,
});
assert.equal(persistResult.accepted, true);
assert.equal(persistResult.storageTier, "authority-sql");
assert.equal(persistResult.acceptedBy, "authority-sql");
assert.equal(
Number(
harness.api.getAuthoritySnapshotForChat("chat-authority-sql-storage-only")?.meta
?.revision || 0,
),
persistResult.revision,
"SQL-only Authority capability should still perform accepted Authority SQL graph persistence",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-b",
globalChatId: "chat-b",
chatMetadata: {
integrity: "meta-chat-b",
},
});
harness.api.setCurrentGraph(createMeaningfulGraph("chat-a", "queued"));
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-a",
revision: 6,
lastPersistedRevision: 4,
queuedPersistRevision: 6,
queuedPersistChatId: "chat-a",
queuedPersistMode: "immediate",
pendingPersist: true,
writesBlocked: false,
});
const result = harness.api.maybeFlushQueuedGraphPersist("cross-chat-flush");
assert.equal(result.saved, false);
assert.equal(result.blocked, true);
assert.equal(result.reason, "queued-chat-mismatch");
assert.equal(harness.runtimeContext.__contextImmediateSaveCalls, 0);
assert.equal(harness.runtimeContext.__contextSaveCalls, 0);
assert.equal(
harness.runtimeContext.__chatContext.chatMetadata?.st_bme_graph,
undefined,
"跨 chat 的 queued persist 不得 flush 到当前 metadata",
);
assert.equal(
harness.api.getGraphPersistenceLiveState().queuedPersistChatId,
"chat-a",
"发生 chat mismatch 时应保留原始 queued chat 绑定",
);
}
// === Fix 2c: assertRecoveryChatStillActive 跨 chat 守卫 ===
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-recovery-a",
globalChatId: "chat-recovery-a",
chatMetadata: {
integrity: "meta-recovery-a",
},
});
// 同一 chat 不应抛出
harness.api.assertRecoveryChatStillActive("chat-recovery-a", "test-same");
// 切换到 chat-b
harness.runtimeContext.__globalChatId = "chat-recovery-b";
harness.runtimeContext.__chatContext.chatId = "chat-recovery-b";
let abortCaught = false;
try {
harness.api.assertRecoveryChatStillActive("chat-recovery-a", "test-switch");
} catch (e) {
abortCaught = harness.api.isAbortError(e);
}
assert.equal(
abortCaught,
true,
"chat 切换后 assertRecoveryChatStillActive 应抛出 AbortError",
);
// 空 expectedChatId 不应抛出
harness.api.assertRecoveryChatStillActive("", "test-empty");
harness.api.assertRecoveryChatStillActive(undefined, "test-undefined");
}
// === Fix 2e: resolveDirtyFloorFromMutationMeta 候选过滤 ===
// 此测试需要 resolveDirtyFloorFromMutationMeta 与 getAssistantTurns
// 它们均在 persistencePrelude 范围内,通过 vm 上下文执行。
// 这里使用间接方式验证:构造一个只有晚期 assistant 的 chat
// 然后检查 inspectHistoryMutation 不会对早期 floor 误判。
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-dirty-floor",
globalChatId: "chat-dirty-floor",
chatMetadata: {
integrity: "meta-dirty-floor",
},
chat: [
// index 0: user
{ is_user: true, mes: "hello" },
// index 1: user (no assistant before index 4)
{ is_user: true, mes: "second" },
// index 2: user
{ is_user: true, mes: "third" },
// index 3: user
{ is_user: true, mes: "fourth" },
// index 4: first assistant
{ is_user: false, mes: "first reply" },
],
});
const graph = createMeaningfulGraph("chat-dirty-floor", "dirty-floor");
graph.historyState.lastProcessedAssistantFloor = 4;
graph.historyState.extractionCount = 1;
harness.api.setCurrentGraph(graph);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-dirty-floor",
revision: 2,
writesBlocked: false,
});
// 模拟meta 指向 floor=1早于最小可提取 floor=4的删除事件
// 使用间接方式graph 的 lastProcessedAssistantFloor=4
// 如果 resolveDirtyFloorFromMutationMeta 正确过滤了 floor<4 的候选,
// 那么 inspectHistoryMutation 不会标记为 dirty因为没有有效候选
// 注意:这里不直接测试内部函数,而是验证整体行为。
const graph2 = harness.api.getCurrentGraph();
assert.ok(graph2, "graph 应存在");
assert.equal(
graph2.historyState.lastProcessedAssistantFloor,
4,
"lastProcessedAssistantFloor 应为 4",
);
}
{
const metadataGraph = stampPersistedGraph(
createMeaningfulGraph("chat-indexeddb-priority", "metadata"),
{
revision: 3,
integrity: "meta-indexeddb-priority",
chatId: "chat-indexeddb-priority",
reason: "metadata-seed",
},
);
const indexedDbGraph = stampPersistedGraph(
createMeaningfulGraph("chat-indexeddb-priority", "indexeddb"),
{
revision: 9,
integrity: "idxdb-indexeddb-priority",
chatId: "chat-indexeddb-priority",
reason: "indexeddb-seed",
},
);
const indexedDbSnapshot = buildSnapshotFromGraph(indexedDbGraph, {
chatId: "chat-indexeddb-priority",
revision: 9,
});
const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-priority",
globalChatId: "chat-indexeddb-priority",
chatMetadata: {
integrity: "meta-indexeddb-priority",
[GRAPH_METADATA_KEY]: metadataGraph,
},
indexedDbSnapshot,
});
harness.api.loadGraphFromChat({ source: "indexeddb-priority" });
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(harness.api.getCurrentGraph().nodes[0].id, "node-indexeddb");
assert.equal(
harness.api.getGraphPersistenceState().storagePrimary,
"indexeddb",
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-shadow-restore",
globalChatId: "chat-indexeddb-shadow-restore",
sessionStore: sharedSession,
});
writer.api.writeGraphShadowSnapshot(
"chat-indexeddb-shadow-restore",
createMeaningfulGraph("chat-indexeddb-shadow-restore", "shadow-newer"),
{
revision: 9,
reason: "pagehide-refresh",
},
);
const indexedDbGraph = stampPersistedGraph(
createMeaningfulGraph("chat-indexeddb-shadow-restore", "indexeddb-older"),
{
revision: 4,
integrity: "meta-indexeddb-shadow-restore",
chatId: "chat-indexeddb-shadow-restore",
reason: "indexeddb-older",
},
);
const indexedDbSnapshot = buildSnapshotFromGraph(indexedDbGraph, {
chatId: "chat-indexeddb-shadow-restore",
revision: 4,
});
const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-shadow-restore",
globalChatId: "chat-indexeddb-shadow-restore",
indexedDbSnapshot,
sessionStore: sharedSession,
});
const result = await harness.api.loadGraphFromIndexedDb(
"chat-indexeddb-shadow-restore",
{
source: "indexeddb-shadow-restore",
allowOverride: true,
applyEmptyState: true,
},
);
assert.equal(result.loadState, "shadow-restored");
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-shadow-newer",
);
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
harness.api.getIndexedDbSnapshot().meta.revision,
9,
"shadow 恢复后应回补 IndexedDB 修正旧快照",
);
}
{
const legacyGraph = stampPersistedGraph(
createMeaningfulGraph("chat-legacy-migration", "legacy"),
{
revision: 6,
integrity: "meta-legacy-migration",
chatId: "chat-legacy-migration",
reason: "legacy-seed",
},
);
const harness = await createGraphPersistenceHarness({
chatId: "chat-legacy-migration",
globalChatId: "chat-legacy-migration",
chatMetadata: {
integrity: "meta-legacy-migration",
[GRAPH_METADATA_KEY]: legacyGraph,
},
indexedDbSnapshot: {
meta: {
chatId: "chat-legacy-migration",
revision: 0,
migrationCompletedAt: 0,
},
nodes: [],
edges: [],
tombstones: [],
state: {
lastProcessedFloor: -1,
extractionCount: 0,
},
},
});
harness.api.loadGraphFromChat({ source: "legacy-migration-check" });
await new Promise((resolve) => setTimeout(resolve, 0));
assert.ok(harness.runtimeContext.__syncNowCalls.length >= 1);
assert.equal(
harness.runtimeContext.__syncNowCalls[0].options.reason,
"post-migration",
);
assert.equal(harness.api.getCurrentGraph().nodes[0].id, "node-legacy");
assert.equal(
harness.api.getIndexedDbSnapshot().meta.migrationSource,
"chat_metadata",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-state-save",
globalChatId: "chat-state-save",
chatMetadata: {
integrity: "meta-chat-state-save",
},
indexedDbSnapshot: {
meta: {
chatId: "chat-state-save",
revision: 0,
},
nodes: [],
edges: [],
tombstones: [],
state: {
lastProcessedFloor: -1,
extractionCount: 0,
},
},
});
const graph = stampPersistedGraph(
createMeaningfulGraph("chat-state-save", "sidecar"),
{
revision: 7,
integrity: "meta-chat-state-save",
chatId: "chat-state-save",
reason: "chat-state-seed",
},
);
const result = await harness.runtimeContext.persistGraphToHostChatState(
harness.runtimeContext.__chatContext,
{
graph,
revision: 7,
reason: "chat-state-direct-save",
storageTier: "chat-state",
accepted: true,
lastProcessedAssistantFloor: 6,
extractionCount: 3,
mode: "primary",
},
);
assert.equal(result.saved, true);
const stored = await harness.runtimeContext.__chatContext.getChatState(
GRAPH_CHAT_STATE_NAMESPACE,
);
assert.equal(stored?.revision, 7);
assert.equal(stored?.commitMarker?.storageTier, "chat-state");
assert.equal(
harness.api.getGraphPersistenceState().dualWriteLastResult?.target,
"chat-state",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-state-read",
globalChatId: "chat-state-read",
chatMetadata: {
integrity: "meta-chat-state-read",
},
});
const sidecarGraph = stampPersistedGraph(
createMeaningfulGraph("chat-state-read", "sidecar-read"),
{
revision: 9,
integrity: "meta-chat-state-read",
chatId: "chat-state-read",
reason: "chat-state-read-seed",
},
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
GRAPH_CHAT_STATE_NAMESPACE,
buildGraphChatStateSnapshot(sidecarGraph, {
revision: 9,
storageTier: "chat-state",
accepted: true,
reason: "chat-state-read-seed",
chatId: "chat-state-read",
integrity: "meta-chat-state-read",
lastProcessedAssistantFloor: 6,
extractionCount: 3,
}),
);
const result = await harness.runtimeContext.readGraphChatStateSnapshot(
harness.runtimeContext.__chatContext,
{
namespace: GRAPH_CHAT_STATE_NAMESPACE,
},
);
assert.equal(
harness.runtimeContext.canUseGraphChatState(
harness.runtimeContext.__chatContext,
),
true,
);
assert.equal(result?.revision, 9);
assert.equal(result?.commitMarker?.storageTier, "chat-state");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-generic-primary-no-mirror",
globalChatId: "chat-generic-primary-no-mirror",
characterId: "char-generic",
chatMetadata: {
integrity: "meta-generic-primary-no-mirror",
},
});
const graph = stampPersistedGraph(
createMeaningfulGraph("chat-generic-primary-no-mirror", "generic-primary"),
{
revision: 5,
integrity: "meta-generic-primary-no-mirror",
chatId: "chat-generic-primary-no-mirror",
reason: "generic-primary-seed",
},
);
harness.api.setCurrentGraph(graph);
const result = await harness.api.persistExtractionBatchResult({
reason: "generic-primary-persist",
lastProcessedAssistantFloor: 6,
});
assert.equal(result.accepted, true);
assert.equal(result.storageTier, "indexeddb");
assert.equal(
harness.runtimeContext.__chatContext.__chatStateStore.size,
0,
"generic ST 主写成功后不应再常驻 mirror 到 chat-state",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-luker-primary",
globalChatId: "chat-luker-primary",
characterId: "char-luker",
chatMetadata: {
integrity: "meta-luker-primary",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const graph = stampPersistedGraph(
createMeaningfulGraph("chat-luker-primary", "luker-primary"),
{
revision: 8,
integrity: "meta-luker-primary",
chatId: "chat-luker-primary",
reason: "luker-primary-seed",
},
);
harness.api.setCurrentGraph(graph);
const result = await harness.api.persistExtractionBatchResult({
reason: "luker-primary-persist",
lastProcessedAssistantFloor: 6,
});
assert.equal(result.accepted, true);
assert.equal(result.storageTier, "luker-chat-state");
assert.equal(result.acceptedBy, "luker-chat-state");
const manifest = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_MANIFEST_NAMESPACE,
);
const journal = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_JOURNAL_NAMESPACE,
);
const checkpoint = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
);
const legacyStored = await harness.runtimeContext.__chatContext.getChatState(
GRAPH_CHAT_STATE_NAMESPACE,
);
assert.equal(manifest?.headRevision, result.revision);
assert.equal(manifest?.formatVersion, 2);
assert.equal(manifest?.storageTier, "luker-chat-state");
assert.equal(manifest?.checkpointRevision, result.revision);
assert.equal(checkpoint?.revision, result.revision);
assert.equal(Array.isArray(journal?.entries), true);
assert.equal(journal?.entries?.length, 0);
assert.equal(legacyStored ?? null, null);
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0),
0,
"Luker 主存储成功后默认不应补写浏览器本地大图谱缓存 revision",
);
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.nodes?.length || 0),
0,
"Luker 主存储成功后默认不应补写浏览器本地大图谱缓存 nodes",
);
assert.equal(result.cacheTier, "none");
assert.equal(
harness.api.getGraphPersistenceState().acceptedStorageTier,
"luker-chat-state",
);
assert.equal(
harness.api.getGraphPersistenceState().lukerManifestRevision,
result.revision,
);
}
{
const chatId = "chat-luker-authority-sql-primary";
const persistenceChatId = "meta-luker-authority-sql-primary";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
characterId: "char-luker-authority-sql",
chatMetadata: {
integrity: persistenceChatId,
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
harness.runtimeContext.extension_settings[MODULE_NAME] = {
authorityEnabled: "on",
authorityPrimaryWhenAvailable: true,
authorityStorageMode: "server-primary",
authoritySqlPrimary: true,
authorityBrowserCacheMode: "minimal",
};
harness.api.setAuthorityCapabilityState({
installed: true,
healthy: true,
sessionReady: true,
permissionReady: true,
minimumFeatureSetReady: true,
serverPrimaryReady: true,
storagePrimaryReady: true,
triviumPrimaryReady: true,
jobsReady: true,
blobReady: true,
features: [
"sql.query",
"sql.mutation",
"trivium.search",
"jobs",
"blob",
],
supportedJobTypes: ["delay"],
supportedJobTypesKnown: true,
reason: "ok",
lastProbeAt: Date.now(),
});
harness.api.setCurrentGraph(
stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-authority-sql"),
{
revision: 9,
integrity: persistenceChatId,
chatId,
reason: "luker-authority-sql-seed",
},
),
);
const result = await harness.api.persistExtractionBatchResult({
reason: "luker-authority-sql-persist",
lastProcessedAssistantFloor: 9,
});
assert.equal(result.accepted, true);
assert.equal(result.storageTier, "authority-sql");
assert.equal(result.acceptedBy, "authority-sql");
assert.equal(result.primaryTier, "authority-sql");
assert.equal(result.cacheTier, "none");
assert.equal(
await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_MANIFEST_NAMESPACE,
),
null,
"Authority SQL primary in Luker must not be preempted by Luker sidecar manifest",
);
assert.equal(
Number(harness.api.getAuthoritySnapshotForChat(persistenceChatId)?.meta?.revision || 0),
result.revision,
"Authority SQL snapshot should receive the accepted persist revision",
);
harness.api.setCurrentGraph(
stampPersistedGraph(
createMeaningfulGraph(chatId, "runtime-stale-checkpoint"),
{
revision: 1,
integrity: persistenceChatId,
chatId,
reason: "runtime-stale-checkpoint",
},
),
);
const checkpointResult = await harness.api.writeAuthorityCheckpointFromCurrentGraph({
reason: "authority-sql-checkpoint-source-test",
});
assert.equal(checkpointResult.success, true);
assert.equal(checkpointResult.result.source, "authority-sql");
assert.equal(checkpointResult.result.checkpointRevision, result.revision);
const checkpointPayload = Array.from(globalThis.__authorityBlobWrites.entries()).at(-1)?.[1];
assert.equal(checkpointPayload?.revision, result.revision);
const checkpointGraph = deserializeGraph(checkpointPayload?.serializedGraph || "{}");
assert.equal(checkpointGraph.nodes[0]?.fields?.title, "事件-luker-authority-sql");
assert.notEqual(checkpointGraph.nodes[0]?.fields?.title, "事件-runtime-stale-checkpoint");
harness.api.setAuthoritySnapshotForChat(persistenceChatId, null);
const writeCountBeforeFailedCheckpoint = globalThis.__authorityBlobWrites.size;
const failedCheckpointResult = await harness.api.writeAuthorityCheckpointFromCurrentGraph({
reason: "authority-sql-checkpoint-source-missing-test",
});
assert.equal(failedCheckpointResult.success, false);
assert.equal(failedCheckpointResult.error, "authority-sql-checkpoint-source-empty");
assert.equal(
globalThis.__authorityBlobWrites.size,
writeCountBeforeFailedCheckpoint,
"Authority SQL canonical checkpoint must fail instead of writing stale runtime graph",
);
}
{
const chatId = "chat-luker-no-authority-primary";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
characterId: "char-luker-no-authority",
chatMetadata: {
integrity: "meta-luker-no-authority-primary",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
harness.runtimeContext.extension_settings[MODULE_NAME] = {
authorityEnabled: "on",
authorityPrimaryWhenAvailable: true,
authorityStorageMode: "server-primary",
authoritySqlPrimary: true,
authorityBrowserCacheMode: "minimal",
};
harness.api.setAuthorityCapabilityState({
installed: false,
healthy: false,
serverPrimaryReady: false,
storagePrimaryReady: false,
reason: "authority-not-installed",
});
harness.api.setCurrentGraph(
stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-no-authority"),
{
revision: 7,
integrity: "meta-luker-no-authority-primary",
chatId,
reason: "luker-no-authority-seed",
},
),
);
const result = await harness.api.persistExtractionBatchResult({
reason: "luker-no-authority-persist",
lastProcessedAssistantFloor: 5,
});
assert.equal(result.accepted, true);
assert.equal(result.storageTier, "luker-chat-state");
assert.equal(result.acceptedBy, "luker-chat-state");
assert.equal(result.primaryTier, "luker-chat-state");
assert.equal(result.cacheTier, "none");
const manifest = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_MANIFEST_NAMESPACE,
);
assert.equal(manifest?.storageTier, "luker-chat-state");
assert.equal(manifest?.headRevision, result.revision);
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0),
0,
"Authority 不可用时Luker 主存储不应回退写浏览器大图谱缓存 revision",
);
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.nodes?.length || 0),
0,
"Authority 不可用时Luker 主存储不应回退写浏览器大图谱缓存 nodes",
);
}
{
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(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0),
0,
"Luker queued save 默认不应写入浏览器本地大图谱缓存 revision",
);
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.nodes?.length || 0),
0,
"Luker queued save 默认不应写入浏览器本地大图谱缓存 nodes",
);
}
{
const chatId = "chat-luker-manual-cache-rebuild";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
characterId: "char-luker-manual-cache-rebuild",
chatMetadata: {
integrity: "meta-luker-manual-cache-rebuild",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const graph = stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-manual-cache"),
{
revision: 10,
integrity: "meta-luker-manual-cache-rebuild",
chatId,
reason: "luker-manual-cache-seed",
},
);
harness.api.setCurrentGraph(graph);
const persistResult = await harness.api.persistExtractionBatchResult({
reason: "luker-manual-cache-persist",
lastProcessedAssistantFloor: 6,
});
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(persistResult.cacheTier, "none");
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0),
0,
"Luker 默认路径不应自动重建浏览器缓存",
);
const rebuildResult = await harness.api.onRebuildLocalCacheFromLukerSidecar();
assert.equal(rebuildResult.handledToast, true);
assert.equal(rebuildResult.result?.loaded, true);
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0),
persistResult.revision,
"只有手动重建本地缓存时才应写入浏览器缓存 revision",
);
assert.equal(
Number(harness.api.getIndexedDbSnapshot()?.nodes?.length || 0),
1,
"只有手动重建本地缓存时才应写入浏览器缓存 nodes",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-luker-v2-load",
globalChatId: "chat-luker-v2-load",
characterId: "char-luker-v2",
chatMetadata: {
integrity: "meta-luker-v2-load",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const graph = stampPersistedGraph(
createMeaningfulGraph("chat-luker-v2-load", "luker-v2-load"),
{
revision: 4,
integrity: "meta-luker-v2-load",
chatId: "chat-luker-v2-load",
reason: "luker-v2-load-seed",
},
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_JOURNAL_NAMESPACE,
buildLukerGraphJournalV2([], {
chatId: "chat-luker-v2-load",
integrity: "meta-luker-v2-load",
headRevision: 4,
}),
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
{
formatVersion: 2,
revision: 4,
serializedGraph: serializeGraph(graph),
chatId: "chat-luker-v2-load",
integrity: "meta-luker-v2-load",
counts: {
nodeCount: 1,
edgeCount: 0,
archivedCount: 0,
tombstoneCount: 0,
},
persistedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
reason: "luker-v2-load-seed",
storageTier: "luker-chat-state",
},
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_MANIFEST_NAMESPACE,
buildLukerGraphManifestV2(graph, {
baseRevision: 4,
headRevision: 4,
checkpointRevision: 4,
lastCompactedRevision: 4,
journalDepth: 0,
journalBytes: 0,
chatId: "chat-luker-v2-load",
integrity: "meta-luker-v2-load",
reason: "luker-v2-load-seed",
storageTier: "luker-chat-state",
accepted: true,
lastProcessedAssistantFloor: 6,
extractionCount: 3,
}),
);
const sidecar = await harness.runtimeContext.readLukerGraphSidecarV2(
harness.runtimeContext.__chatContext,
);
assert.equal(Number(sidecar?.manifest?.headRevision || 0), 4);
assert.equal(Number(sidecar?.checkpoint?.revision || 0), 4);
assert.equal(Number(sidecar?.journal?.entryCount || 0), 0);
assert.equal(
sidecar?.manifest?.chatId,
"chat-luker-v2-load",
);
assert.equal(
sidecar?.checkpoint?.chatId,
"chat-luker-v2-load",
);
}
{
const chatId = "chat-luker-revision-drift";
const integrity = "meta-luker-revision-drift";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
characterId: "char-luker-revision-drift",
chatMetadata: {
integrity,
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const checkpointGraph = stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-revision-base"),
{
revision: 1,
integrity,
chatId,
reason: "luker-revision-base",
},
);
const runtimeGraph = stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-revision-next"),
{
revision: 3,
integrity,
chatId,
reason: "luker-revision-next",
},
);
harness.api.setCurrentGraph(runtimeGraph);
harness.api.setGraphPersistenceState({
hostProfile: "luker",
primaryStorageTier: "luker-chat-state",
cacheStorageTier: "indexeddb",
revision: 3,
lastPersistedRevision: 3,
lastAcceptedRevision: 3,
});
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
buildLukerGraphCheckpointV2(checkpointGraph, {
revision: 1,
chatId,
integrity,
reason: "luker-revision-base",
storageTier: "luker-chat-state",
}),
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_JOURNAL_NAMESPACE,
buildLukerGraphJournalV2([], {
chatId,
integrity,
headRevision: 1,
}),
);
harness.runtimeContext.__chatContext.__chatStateStore.set(
LUKER_GRAPH_MANIFEST_NAMESPACE,
buildLukerGraphManifestV2(checkpointGraph, {
baseRevision: 1,
headRevision: 1,
checkpointRevision: 1,
lastCompactedRevision: 1,
journalDepth: 0,
journalBytes: 0,
chatId,
integrity,
reason: "luker-revision-base",
storageTier: "luker-chat-state",
accepted: true,
lastProcessedAssistantFloor: 2,
extractionCount: 1,
}),
);
const baseSnapshot = buildSnapshotFromGraph(checkpointGraph, {
chatId,
revision: 1,
});
const driftedSnapshot = buildSnapshotFromGraph(runtimeGraph, {
chatId,
revision: 3,
});
const directDelta = buildPersistDelta(baseSnapshot, driftedSnapshot, {
useNativeDelta: false,
});
const result = await harness.runtimeContext.persistGraphToHostChatState(
harness.runtimeContext.__chatContext,
{
graph: runtimeGraph,
revision: 3,
reason: "luker-revision-drift-save",
storageTier: "luker-chat-state",
accepted: true,
lastProcessedAssistantFloor: 4,
extractionCount: 2,
mode: "primary",
persistDelta: directDelta,
},
);
assert.equal(result.saved, true);
assert.equal(
result.revision,
2,
"Luker sidecar 应基于已接受 head 连续推进,而不是沿用跳号 revision",
);
const manifest = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_MANIFEST_NAMESPACE,
);
const journal = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_JOURNAL_NAMESPACE,
);
assert.equal(Number(manifest?.headRevision || 0), 2);
assert.equal(Number(journal?.entries?.length || 0), 1);
assert.equal(Number(journal?.entries?.[0]?.revision || 0), 2);
}
{
const chatId = "chat-luker-bootstrap-journal-fail";
const integrity = "meta-luker-bootstrap-journal-fail";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
characterId: "char-luker-bootstrap-journal-fail",
chatMetadata: {
integrity,
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const graph = stampPersistedGraph(
createMeaningfulGraph(chatId, "luker-bootstrap-journal-fail"),
{
revision: 5,
integrity,
chatId,
reason: "luker-bootstrap-journal-fail",
},
);
const originalUpdateChatState = harness.runtimeContext.__chatContext.updateChatState;
harness.runtimeContext.__chatContext.updateChatState = async function(namespace, updater) {
const key = String(namespace || "").trim().toLowerCase();
if (key === LUKER_GRAPH_JOURNAL_NAMESPACE) {
return { ok: false, state: null, updated: false };
}
return await originalUpdateChatState.call(this, namespace, updater);
};
const result = await harness.runtimeContext.persistGraphToHostChatState(
harness.runtimeContext.__chatContext,
{
graph,
revision: 5,
reason: "luker-bootstrap-journal-fail",
storageTier: "luker-chat-state",
accepted: true,
lastProcessedAssistantFloor: 3,
extractionCount: 1,
mode: "primary",
},
);
assert.equal(result.saved, false);
assert.equal(result.accepted, false);
const manifest = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_MANIFEST_NAMESPACE,
);
const checkpoint = await harness.runtimeContext.__chatContext.getChatState(
LUKER_GRAPH_CHECKPOINT_NAMESPACE,
);
assert.equal(
manifest ?? null,
null,
"bootstrap journal reset 失败时不应继续写 manifest 假装 accepted",
);
assert.equal(Number(checkpoint?.revision || 0), 5);
}
{
const chatId = "chat-luker-targeted-write";
const integrity = "meta-luker-targeted-write";
const harness = await createGraphPersistenceHarness({
chatId,
globalChatId: chatId,
groupId: "group-luker-targeted-write",
chatMetadata: {
integrity,
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
const branchTarget = {
is_group: true,
id: "group-luker-targeted-branch",
};
const graph = stampPersistedGraph(
createMeaningfulGraph("group-luker-targeted-branch", "luker-targeted-write"),
{
revision: 2,
integrity,
chatId: "group-luker-targeted-branch",
reason: "luker-targeted-write",
},
);
const result = await harness.runtimeContext.persistGraphToHostChatState(
harness.runtimeContext.__chatContext,
{
graph,
chatId: "group-luker-targeted-branch",
revision: 2,
reason: "luker-targeted-write",
storageTier: "luker-chat-state",
accepted: true,
lastProcessedAssistantFloor: 6,
extractionCount: 3,
mode: "primary",
chatStateTarget: branchTarget,
},
);
assert.equal(result.saved, true);
assert.equal(result.accepted, true);
const targetedCalls = harness.runtimeContext.__chatContext.__chatStateCalls.filter(
(call) => call.type === "update" && call.target?.id === branchTarget.id,
);
assert.ok(
targetedCalls.length >= 3,
"显式 chatStateTarget 写入 Luker sidecar 时应把 target 传给 manifest/journal/checkpoint 链路",
);
}
console.log("graph-persistence tests passed");