- 帧壳 `turnId` 在 socket 层不再被丢(`lib/ws.ts` 的 dispatchFrame 补 turnId/seq)—— 按路分桶的前提 - 并行帧组接入:`turn_begin` / `steer_ack` / `pending_ack` / `pending_drain` / `pending_full` / `chat_duplicate` ⇒ 六态 + 灰态 + `(sessionId, turnId)` 两级分桶;`appendChunk` 不再永远写尾气泡 - 多路忙态:`session_interrupted` 不再无条件当空闲(看 mode/activeTurns);`stream_end` 不再只按会话过滤 - `documents` 渲染(含 status/chars/truncated 与**逐字中文 reason**);`turnId` 历史行分组小标 - `System.tsx` 键名改正(真键 HOST/PORT/ADMIN_USERNAME/ADMIN_PASSWORD,避免照抄静默失效) - 主人档案**按平台分组** + 平台下拉 + 账号候选;**写盘仍展平数组**(后端契约不变) - 验证:tsc --noEmit ✅、npm run build ✅、22 个前端测试文件全通过(本次新增 3 文件 108 条断言) - 未做(如实):握手 history handler 与断线补拉、上行 interrupt 按钮、proactive_message/subagent_result 等帧落点、文档上传入口与图片预检、voice 管线、System 的 env 读端点
48 lines
2.2 KiB
TypeScript
48 lines
2.2 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: 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<AppStore>;
|
|
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,
|
|
}),
|
|
};
|