import type { AppStore } from "./useAppStore"; /** * zustand `persist` 中间件的配置(从 `useAppStore.ts` 拆出,AGENTS §10 文件 ≤500 行)。 * * 只持久化「小且可重建」的东西:会话元数据 + 聚焦的会话 id + 角色目录 + 情绪 + 上下文 + * 后台思考条目。消息**不持久化**(后端是消息真源,design/16 §1)。v1 的形状(整份聊天 + * 单个会话 id)与 v2(一个全局 `currentPersona`)都无法描述「角色属于会话」的现状, * 统一丢弃并由接口重新取回。 */ export const persistOptions = { name: "cyrene.app", version: 3, // v1 persisted the whole chat (`messages` + a single `sessionId`); v2 added // one resolved `currentPersona`, which can no longer describe the active role // now that the role belongs to a *session*. Both shapes are dropped: // `personas` is refilled from `GET /personas` and the role is re-derived from // the session list. `moods` is transient by nature (a restart resets a role's // mood), so it is not carried across either. A v1 state was never trusted for // the session metadata, so those users simply restart on the main session. migrate: (persisted: unknown, version: number) => { const p = (persisted ?? {}) as Partial; const fromV1 = version < 2; return { activeSessionId: fromV1 ? "" : p.activeSessionId ?? "", sessions: fromV1 ? {} : p.sessions ?? {}, personas: p.personas ?? [], moods: {}, context: p.context ?? {}, backthink: p.backthink ?? [], }; }, partialize: (s: AppStore) => ({ // messages deliberately excluded: the backend is the message truth // (design/16 §1), so we never persist the whole chat into localStorage — // and neither is `sessionMessages` (the per-session buckets of 裁决 16.4.5). // We keep the (small) session metadata + the focused session id so a // refresh lands on the same conversation, plus the role catalog so the // avatar/name/wallpaper resolve before the first fetch returns. activeSessionId: s.activeSessionId, sessions: s.sessions, personas: s.personas, moods: s.moods, context: s.context, backthink: s.backthink, }), };