diff --git a/index.js b/index.js index 1066c6f..3bed3ac 100644 --- a/index.js +++ b/index.js @@ -1517,7 +1517,7 @@ function buildPersistenceEnvironment( cacheStorageTier: authorityPrimary ? "none" : hostProfile === "luker" - ? localStoreTier + ? "none" : "none", }; } @@ -24262,4 +24262,3 @@ async function onCompactLukerSidecar() { debugLog("[ST-BME] 初始化完成"); })(); - diff --git a/manifest.json b/manifest.json index c47998e..f653204 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "js": "index.js", "css": "style.css", "author": "Youzini", - "version": "6.2.7", + "version": "6.2.8", "homePage": "https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology" } diff --git a/tests/graph-persistence.mjs b/tests/graph-persistence.mjs index a2db2ee..b94661e 100644 --- a/tests/graph-persistence.mjs +++ b/tests/graph-persistence.mjs @@ -159,6 +159,10 @@ const messageSnippet = extractSnippet( 'function onMessageReceived(messageId = null, type = "") {', "async function onViewGraph() {", ); +const lukerCacheActionSnippet = extractSnippet( + "async function onRebuildLocalCacheFromLukerSidecar() {", + "async function onRepairLukerSidecar() {", +); function createSessionStorage(seed = null) { const store = seed instanceof Map ? seed : new Map(); @@ -1555,6 +1559,7 @@ async function createGraphPersistenceHarness({ persistencePrelude, persistenceCore, messageSnippet, + lukerCacheActionSnippet, ` result = { GRAPH_LOAD_STATES, @@ -1576,6 +1581,7 @@ result = { maybeFlushQueuedGraphPersist, retryPendingGraphPersist, persistExtractionBatchResult, + onRebuildLocalCacheFromLukerSidecar, saveGraphToIndexedDb, cloneGraphForPersistence, assertRecoveryChatStillActive, @@ -4505,10 +4511,16 @@ result = { assert.equal(legacyStored ?? null, null); await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal( - Number(harness.api.getIndexedDbSnapshot()?.meta?.revision || 0) >= result.revision, - true, - "Luker 主存储成功后应异步补写本地缓存", + 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", @@ -4519,6 +4531,74 @@ result = { ); } +{ + 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", @@ -4566,9 +4646,68 @@ result = { await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal( - harness.api.getIndexedDbSnapshot()?.nodes?.[0]?.fields?.title, - "事件-luker-detached", - "Luker queued save 的异步本地 mirror 不应被后续 live graph 修改污染", + 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", ); } diff --git a/ui/panel.js b/ui/panel.js index 61ede94..b9ee6b0 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -3326,6 +3326,7 @@ function _refreshTaskPersistence() { ["Journal", journalStateLabel], ["Checkpoint rev", checkpointRevisionLabel], ["缓存落后", cacheLagLabel], + ["浏览器缓存", ps.cacheStorageTier === "none" ? "已关闭(推荐)" : `${cacheTierLabel} · ${mirrorLabel}`], ); } diagnosticRows.push(