06 - 数据存储¶
插件通过 SealDice 的 ext.storageGet / storageSet 以 JSON 字符串持久化数据。存储键(带不带 aiplugin4_ 前缀取决于 SealDice 扩展命名,代码内统一用 ext.storage*)清单:
| 存储键 | 内容 | 写入方 |
|---|---|---|
storage_version |
存储结构版本号(当前 2) | src/index.ts 启动时 |
agent_<name> |
智能体配置(默认 agent_*、agent_compress_agent、agent_summarize_agent) |
Agent.save |
session_<sid> |
会话完整状态:context、memory、setting(overrides 为 .ai ctxset 的按会话窗口/截断覆盖)、imageManager、stream、bucket、tool、state 等(lastCtx 排除);上下文侧另含 archiveSeq / rawSeq / settledSeq(结算水位)与 lastArchivedAt 等持久化字段 |
Session.save / SessionService.save |
session_<sid>:pending |
挂起队列轻量分片(仅 content/userId/messageId/kind/systemReason/epId/isPrivate,不含运行时 ctx/msg 对象),重载 JS 后按 epId/isPrivate 重建上下文续跑;队列清空时写空串 | Session.savePending / Session.clearPending |
session_sub:<父会话ID>:<childId> |
子代理 child 会话载荷(完整序列化;删除时写空串) | 子代理存储 SubAgentStore.saveChildSession |
subagent_index:<父会话ID> |
子代理记录索引(ChildRecord 数组,保留上限 100,自动清理最旧已结束记录) | 子代理存储 SubAgentStore |
subagent_seq:<父会话ID> |
子代理单调序号计数器(删除记录不回退,保证 childId 不复用) | 子代理存储 SubAgentStore |
subagent:roots |
产生过子代理的父会话根索引(重载清扫/对账用) | 子代理存储 SubAgentStore |
group_<gid> |
群档案:群名、角色、群主、管理员、成员、忽略名单 | Group.save |
user_<uid> |
用户档案:用户名 | User.save |
cmdPriv |
自定义命令权限 | PrivilegeManager.saveCmdPriv |
timerQueue |
定时器队列 | TimerManager.saveTimerQueue |
mediaTaskQueue |
媒体生成任务队列(MediaTask 数组:序号/会话/来源/状态/时间/参数摘要/结果句柄/错误/是否已呈现),每会话最多保留 50 条已结束任务;只存句柄与短摘要,不落 base64 |
MediaTaskManager.save |
usageMap |
token 用量({models: 模型 → 日期 → 计数, purposes: 用途 → 日期 → 计数};旧格式为纯模型映射,读取时自动升级) |
UsageManager.saveUsageMap |
session_<sid>:arc:<三位序号> |
归档分片:移出上下文的群聊原文(条目含 messageId/userId/time/text),只写一次、永不重写;序号由 session.archiveSeq 记录;上限见「原文分片上限」(默认 200,超出时最旧的置空)、按「原文分片保留天数」(默认 30)惰性淘汰;由 read_raw kind=user id=arc:序号 回查 |
Context.writeArchiveShard / src/context/archive_store.ts |
session_<sid>:raw:<序号> |
窗口移出分片:上下文超过「窗口上限字数」时移出的原文(条目结构同归档分片),只写一次、永不重写;序号由 session.rawSeq 记录;上限与 TTL 同上;由 read_raw kind=user id=raw:序号 回查 |
Context.trimToWindow / src/context/archive_store.ts |
memv2:<bankId>:meta |
记忆 v2 元信息分片(meta) | 记忆 v2 仓库 MemoryRepository |
memv2:<bankId>:units |
记忆 v2 单元分片(units) | 记忆 v2 仓库 MemoryRepository |
memv2:<bankId>:links |
记忆 v2 关联分片(links) | 记忆 v2 仓库 MemoryRepository |
memv2:<bankId>:docs |
记忆 v2 文档分片(documents + chunks) | 记忆 v2 仓库 MemoryRepository |
memv2:<bankId>:obs |
记忆 v2 观察分片(observations + entities + mentalModels) | 记忆 v2 仓库 MemoryRepository |
pubSessions |
公开会话目录(JSON:platform/botId/sid/title/addedBy 等条目数组,幂等 add) | src/pub/registry.ts |
modelPurposeOverrides |
全局分用途模型覆盖(use → 模型标识),.ai model 写入,余额不足自动切模型也写 |
src/config/configs/model.ts |
version |
上次运行的插件版本(用于启动更新日志) | checkUpdate |
会话 ID 与档案 ID 格式¶
- 私聊会话:
QQ:<用户ID>,例如QQ:1234567890。 - 群聊会话:
QQ-Group:<群号>,例如QQ-Group:1234。 - 群档案 ID 与群会话 ID 相同;用户档案 ID 与私聊会话 ID 相同。
- 工具调用时常见
QQ:前缀归一(ob11 接口传参前会replace(/^.+:/, ''))。
序列化与反序列化(revive)¶
存储内容不是直接 JSON.parse 后使用,而是经 src/utils/utils.ts 的 revive() 恢复:
- 每个可持久化类声明静态
validKeysMap,描述每个字段的类型(字符串/数字/布尔/数组/嵌套对象/嵌套类)。 revive用new constructor()先建默认实例,再按validKeysMap逐个字段恢复,跳过缺失或类型不符的字段,嵌套类递归恢复。- 这保证存档缺字段/增字段时能平滑兼容;
Context.reviveMessages()、MemoryService.reviveMemoryMap()等再做消息与记忆条目的二次清洗。
版本迁移¶
STORAGE_VERSION = 2写入storage_version;代码里目前只是记录版本,存储迁移按存储键自动进行。mediaTaskQueue(4.24.0 起)的启动恢复:读盘时把running任务标记为failed('插件重载中断')(已发出的 HTTP 请求无法续跑),并对done && !notified且来源为工具的任务补一次通知;参考图 base64 只存内存、不落盘,重载后待执行任务会以「参考图已失效,请重新提交」失败。- 记忆 v2 旧格式单 key(
memv2:<bankId>:bank)首次读取时一次性迁移为 5 个分片 key(meta/units/links/docs/obs),迁移完成后清空旧 key;旧会话挂起队列(session_<sid>内嵌 pendingQueue)在 4.19.3 起不再读写,改由session_<sid>:pending轻量分片承载,读取不到分片时视为空队列。 - 记忆旧格式迁移由
src/memory/v2/migrate.ts的migrateLegacyMemory负责:把旧存档的memoryMap(长期记忆)、summaries(旧短期/观察记忆)与persona分别迁移为新引擎的 MemoryUnit / Observation / 心智模型;旧 persona 另由MemoryManager.migrateLegacyPersona在首次构建长期记忆提示时懒迁移(幂等,已有该问题的心智模型时只清空 persona)。 - 插件自身版本升级通过
version存储键对比VERSION,输出src/changelog.ts中对应版本的更新日志。