mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
refactor: batch 5 phase 2 - extract graph-persistence.js (20 exports, ~230 lines) from index.js
This commit is contained in:
238
graph-persistence.js
Normal file
238
graph-persistence.js
Normal file
@@ -0,0 +1,238 @@
|
||||
// ST-BME: 图谱持久化常量与纯工具函数
|
||||
// 不依赖 index.js 模块级可变状态(currentGraph / graphPersistenceState 等)
|
||||
|
||||
import {
|
||||
deserializeGraph,
|
||||
serializeGraph,
|
||||
} from "./graph.js";
|
||||
import { normalizeGraphRuntimeState } from "./runtime-state.js";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 常量
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
export const MODULE_NAME = "st_bme";
|
||||
export const GRAPH_METADATA_KEY = "st_bme_graph";
|
||||
export const GRAPH_PERSISTENCE_META_KEY = "__stBmePersistence";
|
||||
export const GRAPH_LOAD_STATES = Object.freeze({
|
||||
NO_CHAT: "no-chat",
|
||||
LOADING: "loading",
|
||||
LOADED: "loaded",
|
||||
SHADOW_RESTORED: "shadow-restored",
|
||||
EMPTY_CONFIRMED: "empty-confirmed",
|
||||
BLOCKED: "blocked",
|
||||
});
|
||||
export const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
|
||||
export const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
||||
export const GRAPH_STARTUP_RECONCILE_DELAYS_MS = [150, 600, 1800, 4000];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 纯工具
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
export function cloneRuntimeDebugValue(value, fallback = null) {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch {
|
||||
return fallback ?? value;
|
||||
}
|
||||
}
|
||||
|
||||
export function createLocalIntegritySlug() {
|
||||
const nativeUuid = globalThis.crypto?.randomUUID?.();
|
||||
if (nativeUuid) return nativeUuid;
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
|
||||
const random = Math.floor(Math.random() * 16);
|
||||
const value = char === "x" ? random : (random & 0x3) | 0x8;
|
||||
return value.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
export const GRAPH_PERSISTENCE_SESSION_ID = createLocalIntegritySlug();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 图谱持久化元数据
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @param {object} graph
|
||||
* @returns {object|null}
|
||||
*/
|
||||
export function getGraphPersistenceMeta(graph) {
|
||||
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
||||
return null;
|
||||
}
|
||||
const meta = graph[GRAPH_PERSISTENCE_META_KEY];
|
||||
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} graph
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getGraphPersistedRevision(graph) {
|
||||
const revision = Number(getGraphPersistenceMeta(graph)?.revision);
|
||||
return Number.isFinite(revision) && revision > 0 ? revision : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} graph
|
||||
* @param {object} opts
|
||||
* @param {number} [opts.revision]
|
||||
* @param {string} [opts.reason]
|
||||
* @param {string} [opts.chatId]
|
||||
* @param {string} [opts.integrity]
|
||||
*/
|
||||
export function stampGraphPersistenceMeta(
|
||||
graph,
|
||||
{ revision = 0, reason = "", chatId = "", integrity = "" } = {},
|
||||
) {
|
||||
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existingMeta = getGraphPersistenceMeta(graph) || {};
|
||||
const nextMeta = {
|
||||
...existingMeta,
|
||||
revision: Number.isFinite(revision) && revision > 0 ? revision : 0,
|
||||
updatedAt: new Date().toISOString(),
|
||||
sessionId: GRAPH_PERSISTENCE_SESSION_ID,
|
||||
reason: String(reason || ""),
|
||||
chatId: String(chatId || existingMeta.chatId || ""),
|
||||
integrity: String(integrity || existingMeta.integrity || ""),
|
||||
};
|
||||
graph[GRAPH_PERSISTENCE_META_KEY] = nextMeta;
|
||||
return nextMeta;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 聊天元数据
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
export function writeChatMetadataPatch(context, patch = {}) {
|
||||
if (!context) return false;
|
||||
if (typeof context.updateChatMetadata === "function") {
|
||||
context.updateChatMetadata(patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!context.chatMetadata ||
|
||||
typeof context.chatMetadata !== "object" ||
|
||||
Array.isArray(context.chatMetadata)
|
||||
) {
|
||||
context.chatMetadata = {};
|
||||
}
|
||||
Object.assign(context.chatMetadata, patch || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Shadow Snapshot(会话存储)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
export function getGraphShadowSnapshotStorageKey(chatId = "") {
|
||||
const normalizedChatId = String(chatId || "").trim();
|
||||
if (!normalizedChatId) return "";
|
||||
return `${GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX}${encodeURIComponent(normalizedChatId)}`;
|
||||
}
|
||||
|
||||
export function readGraphShadowSnapshot(chatId = "") {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey) return null;
|
||||
|
||||
try {
|
||||
const raw = globalThis.sessionStorage?.getItem(storageKey);
|
||||
if (!raw) return null;
|
||||
const snapshot = JSON.parse(raw);
|
||||
if (
|
||||
!snapshot ||
|
||||
typeof snapshot !== "object" ||
|
||||
String(snapshot.chatId || "") !== String(chatId || "") ||
|
||||
typeof snapshot.serializedGraph !== "string" ||
|
||||
!snapshot.serializedGraph
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
chatId: String(snapshot.chatId || ""),
|
||||
revision: Number.isFinite(snapshot.revision) ? snapshot.revision : 0,
|
||||
serializedGraph: snapshot.serializedGraph,
|
||||
updatedAt: String(snapshot.updatedAt || ""),
|
||||
reason: String(snapshot.reason || ""),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} chatId
|
||||
* @param {object} graph
|
||||
* @param {object} [opts]
|
||||
* @param {number} [opts.revision]
|
||||
* @param {string} [opts.reason]
|
||||
*/
|
||||
export function writeGraphShadowSnapshot(
|
||||
chatId,
|
||||
graph,
|
||||
{ revision = 0, reason = "" } = {},
|
||||
) {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey || !graph) return false;
|
||||
|
||||
try {
|
||||
const serializedGraph = serializeGraph(graph);
|
||||
globalThis.sessionStorage?.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
chatId: String(chatId || ""),
|
||||
revision: Number.isFinite(revision) ? revision : 0,
|
||||
serializedGraph,
|
||||
updatedAt: new Date().toISOString(),
|
||||
reason: String(reason || ""),
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 写入会话图谱临时快照失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeGraphShadowSnapshot(chatId = "") {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey) return false;
|
||||
|
||||
try {
|
||||
globalThis.sessionStorage?.removeItem(storageKey);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 图谱克隆 / 比较
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
export function cloneGraphForPersistence(graph, chatId = "") {
|
||||
return normalizeGraphRuntimeState(
|
||||
deserializeGraph(serializeGraph(graph)),
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldPreferShadowSnapshotOverOfficial(officialGraph, shadowSnapshot) {
|
||||
if (!shadowSnapshot) return false;
|
||||
const shadowRevision = Number(shadowSnapshot.revision || 0);
|
||||
const officialRevision = getGraphPersistedRevision(officialGraph);
|
||||
return shadowRevision > 0 && shadowRevision > officialRevision;
|
||||
}
|
||||
211
index.js
211
index.js
@@ -102,117 +102,39 @@ import {
|
||||
setBatchStageOutcome,
|
||||
shouldRunRecallForTransaction,
|
||||
} from "./ui-status.js";
|
||||
import {
|
||||
cloneGraphForPersistence,
|
||||
cloneRuntimeDebugValue,
|
||||
getGraphPersistenceMeta,
|
||||
getGraphPersistedRevision,
|
||||
getGraphShadowSnapshotStorageKey,
|
||||
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,
|
||||
readGraphShadowSnapshot,
|
||||
removeGraphShadowSnapshot,
|
||||
shouldPreferShadowSnapshotOverOfficial,
|
||||
stampGraphPersistenceMeta,
|
||||
writeChatMetadataPatch,
|
||||
writeGraphShadowSnapshot,
|
||||
} from "./graph-persistence.js";
|
||||
|
||||
// 操控面板模块(动态加载,防止加载失败崩溃整个扩展)
|
||||
let _panelModule = null;
|
||||
let _themesModule = null;
|
||||
|
||||
const MODULE_NAME = "st_bme";
|
||||
const GRAPH_METADATA_KEY = "st_bme_graph";
|
||||
const GRAPH_PERSISTENCE_META_KEY = "__stBmePersistence";
|
||||
const SERVER_SETTINGS_FILENAME = "st-bme-settings.json";
|
||||
const SERVER_SETTINGS_URL = `/user/files/${SERVER_SETTINGS_FILENAME}`;
|
||||
const GRAPH_LOAD_STATES = Object.freeze({
|
||||
NO_CHAT: "no-chat",
|
||||
LOADING: "loading",
|
||||
LOADED: "loaded",
|
||||
SHADOW_RESTORED: "shadow-restored",
|
||||
EMPTY_CONFIRMED: "empty-confirmed",
|
||||
BLOCKED: "blocked",
|
||||
});
|
||||
const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
|
||||
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
||||
const GRAPH_STARTUP_RECONCILE_DELAYS_MS = [150, 600, 1800, 4000];
|
||||
|
||||
function cloneRuntimeDebugValue(value, fallback = null) {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch {
|
||||
return fallback ?? value;
|
||||
}
|
||||
}
|
||||
|
||||
function createLocalIntegritySlug() {
|
||||
const nativeUuid = globalThis.crypto?.randomUUID?.();
|
||||
if (nativeUuid) return nativeUuid;
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
|
||||
const random = Math.floor(Math.random() * 16);
|
||||
const value = char === "x" ? random : (random & 0x3) | 0x8;
|
||||
return value.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
const GRAPH_PERSISTENCE_SESSION_ID = createLocalIntegritySlug();
|
||||
|
||||
function getGraphPersistenceMeta(graph = currentGraph) {
|
||||
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
||||
return null;
|
||||
}
|
||||
const meta = graph[GRAPH_PERSISTENCE_META_KEY];
|
||||
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
function getGraphPersistedRevision(graph = currentGraph) {
|
||||
const revision = Number(getGraphPersistenceMeta(graph)?.revision);
|
||||
return Number.isFinite(revision) && revision > 0 ? revision : 0;
|
||||
}
|
||||
|
||||
function stampGraphPersistenceMeta(
|
||||
graph = currentGraph,
|
||||
{
|
||||
revision = graphPersistenceState.revision,
|
||||
reason = "",
|
||||
chatId = getCurrentChatId(),
|
||||
integrity = "",
|
||||
} = {},
|
||||
) {
|
||||
if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existingMeta = getGraphPersistenceMeta(graph) || {};
|
||||
const nextMeta = {
|
||||
...existingMeta,
|
||||
revision: Number.isFinite(revision) && revision > 0 ? revision : 0,
|
||||
updatedAt: new Date().toISOString(),
|
||||
sessionId: GRAPH_PERSISTENCE_SESSION_ID,
|
||||
reason: String(reason || ""),
|
||||
chatId: String(chatId || existingMeta.chatId || ""),
|
||||
integrity: String(integrity || existingMeta.integrity || ""),
|
||||
};
|
||||
graph[GRAPH_PERSISTENCE_META_KEY] = nextMeta;
|
||||
return nextMeta;
|
||||
}
|
||||
|
||||
function getChatMetadataIntegrity(context = getContext()) {
|
||||
return normalizeChatIdCandidate(context?.chatMetadata?.integrity);
|
||||
}
|
||||
|
||||
function writeChatMetadataPatch(context = getContext(), patch = {}) {
|
||||
if (!context) return false;
|
||||
if (typeof context.updateChatMetadata === "function") {
|
||||
context.updateChatMetadata(patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!context.chatMetadata ||
|
||||
typeof context.chatMetadata !== "object" ||
|
||||
Array.isArray(context.chatMetadata)
|
||||
) {
|
||||
context.chatMetadata = {};
|
||||
}
|
||||
Object.assign(context.chatMetadata, patch || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
function triggerChatMetadataSave(
|
||||
context = getContext(),
|
||||
{ immediate = false } = {},
|
||||
@@ -245,23 +167,6 @@ function triggerChatMetadataSave(
|
||||
return "debounced";
|
||||
}
|
||||
|
||||
function cloneGraphForPersistence(
|
||||
graph = currentGraph,
|
||||
chatId = getCurrentChatId(),
|
||||
) {
|
||||
return normalizeGraphRuntimeState(
|
||||
deserializeGraph(serializeGraph(graph)),
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
function shouldPreferShadowSnapshotOverOfficial(officialGraph, shadowSnapshot) {
|
||||
if (!shadowSnapshot) return false;
|
||||
const shadowRevision = Number(shadowSnapshot.revision || 0);
|
||||
const officialRevision = getGraphPersistedRevision(officialGraph);
|
||||
return shadowRevision > 0 && shadowRevision > officialRevision;
|
||||
}
|
||||
|
||||
function getRuntimeDebugState() {
|
||||
const stateKey = "__stBmeRuntimeDebugState";
|
||||
if (!globalThis[stateKey] || typeof globalThis[stateKey] !== "object") {
|
||||
@@ -493,80 +398,6 @@ const stageAbortControllers = {
|
||||
history: null,
|
||||
};
|
||||
|
||||
function getGraphShadowSnapshotStorageKey(chatId = "") {
|
||||
const normalizedChatId = String(chatId || "").trim();
|
||||
if (!normalizedChatId) return "";
|
||||
return `${GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX}${encodeURIComponent(normalizedChatId)}`;
|
||||
}
|
||||
|
||||
function readGraphShadowSnapshot(chatId = "") {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey) return null;
|
||||
|
||||
try {
|
||||
const raw = globalThis.sessionStorage?.getItem(storageKey);
|
||||
if (!raw) return null;
|
||||
const snapshot = JSON.parse(raw);
|
||||
if (
|
||||
!snapshot ||
|
||||
typeof snapshot !== "object" ||
|
||||
String(snapshot.chatId || "") !== String(chatId || "") ||
|
||||
typeof snapshot.serializedGraph !== "string" ||
|
||||
!snapshot.serializedGraph
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
chatId: String(snapshot.chatId || ""),
|
||||
revision: Number.isFinite(snapshot.revision) ? snapshot.revision : 0,
|
||||
serializedGraph: snapshot.serializedGraph,
|
||||
updatedAt: String(snapshot.updatedAt || ""),
|
||||
reason: String(snapshot.reason || ""),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeGraphShadowSnapshot(
|
||||
chatId = "",
|
||||
graph = currentGraph,
|
||||
{ revision = graphPersistenceState.revision, reason = "" } = {},
|
||||
) {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey || !graph) return false;
|
||||
|
||||
try {
|
||||
const serializedGraph = serializeGraph(graph);
|
||||
globalThis.sessionStorage?.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
chatId: String(chatId || ""),
|
||||
revision: Number.isFinite(revision) ? revision : 0,
|
||||
serializedGraph,
|
||||
updatedAt: new Date().toISOString(),
|
||||
reason: String(reason || ""),
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 写入会话图谱临时快照失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeGraphShadowSnapshot(chatId = "") {
|
||||
const storageKey = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!storageKey) return false;
|
||||
|
||||
try {
|
||||
globalThis.sessionStorage?.removeItem(storageKey);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getGraphPersistenceLiveState() {
|
||||
const snapshot = {
|
||||
loadState: graphPersistenceState.loadState,
|
||||
|
||||
@@ -27,6 +27,27 @@ import {
|
||||
clampFloat,
|
||||
formatRecallContextLine,
|
||||
} from "../ui-status.js";
|
||||
import {
|
||||
cloneGraphForPersistence,
|
||||
cloneRuntimeDebugValue,
|
||||
getGraphPersistenceMeta,
|
||||
getGraphPersistedRevision,
|
||||
getGraphShadowSnapshotStorageKey,
|
||||
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,
|
||||
readGraphShadowSnapshot,
|
||||
removeGraphShadowSnapshot,
|
||||
shouldPreferShadowSnapshotOverOfficial,
|
||||
stampGraphPersistenceMeta,
|
||||
writeChatMetadataPatch,
|
||||
writeGraphShadowSnapshot,
|
||||
} from "../graph-persistence.js";
|
||||
|
||||
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const indexPath = path.resolve(moduleDir, "../index.js");
|
||||
@@ -42,7 +63,7 @@ function extractSnippet(startMarker, endMarker) {
|
||||
}
|
||||
|
||||
const persistencePrelude = extractSnippet(
|
||||
'const MODULE_NAME = "st_bme";',
|
||||
'const SERVER_SETTINGS_FILENAME = "st-bme-settings.json";',
|
||||
"function clearInjectionState(options = {}) {",
|
||||
);
|
||||
const persistenceCore = extractSnippet(
|
||||
@@ -204,6 +225,64 @@ async function createGraphPersistenceHarness({
|
||||
clampInt,
|
||||
clampFloat,
|
||||
formatRecallContextLine,
|
||||
cloneGraphForPersistence,
|
||||
cloneRuntimeDebugValue,
|
||||
getGraphPersistenceMeta,
|
||||
getGraphPersistedRevision,
|
||||
getGraphShadowSnapshotStorageKey,
|
||||
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,
|
||||
readGraphShadowSnapshot,
|
||||
removeGraphShadowSnapshot,
|
||||
shouldPreferShadowSnapshotOverOfficial,
|
||||
stampGraphPersistenceMeta,
|
||||
writeChatMetadataPatch,
|
||||
writeGraphShadowSnapshot,
|
||||
// Shadow snapshot functions need VM-local sessionStorage overrides
|
||||
// because imported versions use the outer globalThis (no sessionStorage)
|
||||
readGraphShadowSnapshot(chatId = "") {
|
||||
const key = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!key) return null;
|
||||
try {
|
||||
const raw = storage.getItem(key);
|
||||
if (!raw) return 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 || ""),
|
||||
};
|
||||
} catch { return null; }
|
||||
},
|
||||
writeGraphShadowSnapshot(chatId = "", graph = null, { revision = 0, reason = "" } = {}) {
|
||||
const key = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!key || !graph) return false;
|
||||
try {
|
||||
storage.setItem(key, JSON.stringify({
|
||||
chatId: String(chatId || ""),
|
||||
revision: Number.isFinite(revision) ? revision : 0,
|
||||
serializedGraph: serializeGraph(graph),
|
||||
updatedAt: new Date().toISOString(),
|
||||
reason: String(reason || ""),
|
||||
}));
|
||||
return true;
|
||||
} catch { return false; }
|
||||
},
|
||||
removeGraphShadowSnapshot(chatId = "") {
|
||||
const key = getGraphShadowSnapshotStorageKey(chatId);
|
||||
if (!key) return false;
|
||||
try { storage.removeItem(key); return true; } catch { return false; }
|
||||
},
|
||||
createDefaultTaskProfiles() {
|
||||
return {
|
||||
extract: { activeProfileId: "default", profiles: [] },
|
||||
|
||||
@@ -30,6 +30,18 @@ import {
|
||||
setBatchStageOutcome,
|
||||
shouldRunRecallForTransaction,
|
||||
} from "../ui-status.js";
|
||||
import {
|
||||
cloneRuntimeDebugValue,
|
||||
GRAPH_LOAD_STATES,
|
||||
GRAPH_METADATA_KEY,
|
||||
GRAPH_PERSISTENCE_META_KEY,
|
||||
GRAPH_PERSISTENCE_SESSION_ID,
|
||||
MODULE_NAME,
|
||||
readGraphShadowSnapshot,
|
||||
stampGraphPersistenceMeta,
|
||||
writeChatMetadataPatch,
|
||||
writeGraphShadowSnapshot,
|
||||
} from "../graph-persistence.js";
|
||||
|
||||
const extensionsShimSource = [
|
||||
"export const extension_settings = globalThis.__p0ExtensionSettings || {};",
|
||||
@@ -276,6 +288,10 @@ function createGenerationRecallHarness() {
|
||||
getStageNoticeTitle,
|
||||
getStageNoticeDuration,
|
||||
normalizeStageNoticeLevel,
|
||||
MODULE_NAME,
|
||||
GRAPH_LOAD_STATES,
|
||||
GRAPH_METADATA_KEY,
|
||||
GRAPH_PERSISTENCE_META_KEY,
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
|
||||
Reference in New Issue
Block a user