Files
Cyrene_v2/frontend/web/src/store/persistOptions.ts
T
AskaEth a6cb44b8a8
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(flow): 链路图本地落盘 + 多轮历史回放(design/19 §P3,契约 v0.73)
主人要求「链路流程应该有本地数据,不然每次重载之后页面就没数据了」:
- 落盘:迁移 0022_flow_graphs(session_id/turn_id/graph_json/时间戳 + 唯一键 upsert)+ internal/runtime/flowgraph(RecordRound/Graph/Latest/Turns/Turn + 保留期 GC)
- 写入:loop.EndRound **每轮一条**(锁外深拷贝后写;失败 WARN 且不打印图内容);只落发布会话白名单(复用 flowSessionAllowed)
- 读回:端点加 turnId + source(live/persisted/none) + turns[](最新在前,空为 []);**内存优先、miss 才落盘**(最新一轮永远最新)
- 前端:flowGraphs 纳入 persistOptions.partialize(**刷新即刻有图**,version 3→4 + migrate)+ 历史轮选择器 + 诚实文案;顺带收 L1 转交的 flow/NodeDetail.tsx 节点日志倒序
- 新 env 键:FLOW_GRAPH_RETENTION=168h / FLOW_GRAPH_GC_INTERVAL=1h
- 变异实测:每 span 一行 / 保留期守卫失效 / partialize 去掉 flowGraphs / 去掉内存优先 / 白名单失效 ⇒ 全部打红
- 3 个禁动面文件的纯追加改动(Flow.tsx 挂选择器、FlowSnapshotHint 纠已成假话的文案、其渲染断言随之更新)由主会话裁定认可
- 登记存疑:图桶持久化无体积上限;useFlowTurns 的 effect 无自动化证据(项目无 jsdom)
2026-10-05 16:19:32 +08:00

57 lines
2.8 KiB
TypeScript

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: 4,
// 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.
// v4 (切片 F2) added `flowGraphs` (the per-session flow-graph buckets): a v3
// state has none, so it starts empty and is refilled by the snapshot hook.
migrate: (persisted: unknown, version: number) => {
const p = (persisted ?? {}) as Partial<AppStore>;
const fromV1 = version < 2;
return {
activeSessionId: fromV1 ? "" : p.activeSessionId ?? "",
sessions: fromV1 ? {} : p.sessions ?? {},
personas: p.personas ?? [],
moods: {},
context: p.context ?? {},
backthink: p.backthink ?? [],
flowGraphs: p.flowGraphs ?? {},
};
},
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,
// v4 (切片 F2): the per-session flow-graph buckets are persisted too, so a
// refresh paints the last-seen graph immediately instead of flashing the
// empty state until the snapshot fetch returns (主人 2026-10-05「每次重载
// 之后页面就没数据了」). Bounded by `MAX_GRAPHS` (store/sessionCache.ts);
// the backend stays the truth and the snapshot/history endpoints refill it.
flowGraphs: s.flowGraphs,
}),
};