fix: fallback persistence chat identity on mobile

This commit is contained in:
Youzini-afk
2026-04-15 16:20:05 +08:00
parent 5149dbc0d7
commit 13d2b04e73
2 changed files with 167 additions and 4 deletions

View File

@@ -439,6 +439,41 @@ function getCurrentChatId(context = getContext()) {
return resolveCurrentChatIdentity(context).chatId; return resolveCurrentChatIdentity(context).chatId;
} }
function resolvePersistenceChatId(
context = getContext(),
graph = currentGraph,
explicitChatId = "",
) {
const directChatId = normalizeChatIdCandidate(explicitChatId);
if (directChatId) return directChatId;
const resolvedIdentity = resolveCurrentChatIdentity(context);
const resolvedChatId = normalizeChatIdCandidate(resolvedIdentity.chatId);
if (resolvedChatId) return resolvedChatId;
const graphMeta = getGraphPersistenceMeta(graph) || {};
const fallbackCandidates = [
graph?.historyState?.chatId,
graphMeta.chatId,
currentGraph?.historyState?.chatId,
getGraphPersistenceMeta(currentGraph)?.chatId,
graphPersistenceState.chatId,
graphPersistenceState.queuedPersistChatId,
graphPersistenceState.commitMarker?.chatId,
context?.chatMetadata?.integrity,
context?.chatMetadata?.chat_id,
context?.chatMetadata?.chatId,
context?.chatMetadata?.session_id,
context?.chatMetadata?.sessionId,
];
return (
fallbackCandidates
.map((candidate) => normalizeChatIdCandidate(candidate))
.find(Boolean) || ""
);
}
function rememberResolvedGraphIdentityAlias( function rememberResolvedGraphIdentityAlias(
context = getContext(), context = getContext(),
persistenceChatId = getCurrentChatId(context), persistenceChatId = getCurrentChatId(context),
@@ -5491,6 +5526,34 @@ function ensureBmeChatManager() {
return bmeChatManager; return bmeChatManager;
} }
function recordLocalPersistEarlyFailure(
reason = "indexeddb-unavailable",
{
chatId = "",
storagePrimary = graphPersistenceState.storagePrimary || "indexeddb",
storageMode = graphPersistenceState.storageMode || "indexeddb",
revision = 0,
} = {},
) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
const normalizedReason = String(reason || "indexeddb-unavailable").trim();
updateGraphPersistenceState({
storagePrimary,
storageMode,
indexedDbLastError: normalizedReason,
dualWriteLastResult: {
action: "save",
target: storagePrimary,
success: false,
chatId: normalizedChatId,
revision: normalizeIndexedDbRevision(revision),
reason: normalizedReason,
at: Date.now(),
},
});
return normalizedReason;
}
function scheduleBmeIndexedDbTask(task) { function scheduleBmeIndexedDbTask(task) {
const scheduler = const scheduler =
typeof globalThis.queueMicrotask === "function" typeof globalThis.queueMicrotask === "function"
@@ -6814,7 +6877,7 @@ async function persistGraphToHostChatState(
}; };
} }
const chatId = getCurrentChatId(context); const chatId = resolvePersistenceChatId(context, graph);
if (!chatId) { if (!chatId) {
return { return {
saved: false, saved: false,
@@ -10035,7 +10098,7 @@ function persistGraphToChatMetadata(
}); });
} }
const chatId = getCurrentChatId(context); const chatId = resolvePersistenceChatId(context, graph);
if (!chatId) { if (!chatId) {
return buildGraphPersistResult({ return buildGraphPersistResult({
saved: false, saved: false,
@@ -10431,8 +10494,12 @@ async function persistExtractionBatchResult({
}); });
} }
const chatId = getCurrentChatId(context); const chatId = resolvePersistenceChatId(context, persistGraph);
if (!chatId) { if (!chatId) {
recordLocalPersistEarlyFailure("missing-chat-id", {
chatId,
revision: 0,
});
return buildGraphPersistResult({ return buildGraphPersistResult({
saved: false, saved: false,
blocked: true, blocked: true,
@@ -12186,6 +12253,10 @@ async function saveGraphToIndexedDb(
) { ) {
const normalizedChatId = normalizeChatIdCandidate(chatId); const normalizedChatId = normalizeChatIdCandidate(chatId);
if (!normalizedChatId || (!graph && !persistDelta)) { if (!normalizedChatId || (!graph && !persistDelta)) {
recordLocalPersistEarlyFailure("indexeddb-missing-chat-graph-or-delta", {
chatId: normalizedChatId,
revision,
});
return { return {
saved: false, saved: false,
chatId: normalizedChatId, chatId: normalizedChatId,
@@ -12200,6 +12271,10 @@ async function saveGraphToIndexedDb(
try { try {
const manager = ensureBmeChatManager(); const manager = ensureBmeChatManager();
if (!manager) { if (!manager) {
recordLocalPersistEarlyFailure("indexeddb-manager-unavailable", {
chatId: normalizedChatId,
revision,
});
return { return {
saved: false, saved: false,
chatId: normalizedChatId, chatId: normalizedChatId,
@@ -12208,6 +12283,18 @@ async function saveGraphToIndexedDb(
}; };
} }
db = await manager.getCurrentDb(normalizedChatId); db = await manager.getCurrentDb(normalizedChatId);
if (!db) {
recordLocalPersistEarlyFailure("indexeddb-db-unavailable", {
chatId: normalizedChatId,
revision,
});
return {
saved: false,
chatId: normalizedChatId,
reason: "indexeddb-db-unavailable",
revision: normalizeIndexedDbRevision(revision),
};
}
localStore = resolveDbGraphStorePresentation(db); localStore = resolveDbGraphStorePresentation(db);
const persistenceEnvironment = buildPersistenceEnvironment(context, localStore); const persistenceEnvironment = buildPersistenceEnvironment(context, localStore);
const localStoreTier = resolveLocalStoreTierFromPresentation(localStore); const localStoreTier = resolveLocalStoreTierFromPresentation(localStore);
@@ -12931,7 +13018,7 @@ function saveGraphToChat(options = {}) {
reason: "missing-context-or-graph", reason: "missing-context-or-graph",
}); });
} }
const chatId = getCurrentChatId(context); const chatId = resolvePersistenceChatId(context, currentGraph);
const { const {
reason = "graph-save", reason = "graph-save",
markMutation = true, markMutation = true,
@@ -12943,6 +13030,10 @@ function saveGraphToChat(options = {}) {
ensureCurrentGraphRuntimeState(); ensureCurrentGraphRuntimeState();
currentGraph.historyState.extractionCount = extractionCount; currentGraph.historyState.extractionCount = extractionCount;
if (!chatId) { if (!chatId) {
recordLocalPersistEarlyFailure("missing-chat-id", {
chatId,
revision: 0,
});
return buildGraphPersistResult({ return buildGraphPersistResult({
saved: false, saved: false,
blocked: true, blocked: true,

View File

@@ -2448,6 +2448,78 @@ result = {
); );
} }
{
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({ const harness = await createGraphPersistenceHarness({
chatId: "chat-indexeddb-read-failed-fallback", chatId: "chat-indexeddb-read-failed-fallback",