Files
inkreach-agent/docs/plugins/11-storage.md
T

89 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# L9 插件存储(SQLite)
> 每个插件一块**私有**持久化空间:一个 SQLite 库 + 一个自由文件目录。
> KV 简单状态、SQL 结构化数据、文件随便放;schema 必须机器可读声明——它是 Agent 自我迭代与 UI 展示的同一份事实源。
## 1. 空间布局
```
userData/plugin-data/<plugin-id>/
data.db ← SQLite 库(WAL 模式):kv 表(宿主管理)+ 插件自建表
data.db-wal ← WAL 伴生文件
<其余自由文件> ← api.storage.dir() 指向这里,插件自行读写
```
- **每插件一库**(不是共享大库):导出/清除/损坏恢复都以插件为单位,跨插件天然隔离。
- 卸载/移除插件**不删数据**(重装即恢复);删除只在设置页点「清除数据」(二次确认)时发生。
## 2. API(全部 async——底层在宿主共享 worker 线程执行,永不阻塞主进程)
```ts
export default definePlugin(async (api) => {
// ① 声明机器可读 schema(使用 db 前必须声明;plugin:check 强制)
api.storage.declareSchema({
kv: { description: '会话级偏好:ui.<key> = 用户界面偏好' },
tables: [
{
name: 'notes',
description: '用户笔记',
columns: [
{ name: 'id', type: 'TEXT', description: '主键,ulid' },
{ name: 'title', type: 'TEXT', description: '标题,非空' },
{ name: 'created_at', type: 'INTEGER', description: '创建时间,Unix 毫秒' }
]
}
]
})
// ② KV:宿主管理的 kv 表(value 走 JSON 编码,适合设置类小状态)
const last = await api.storage.kv.get<string>('ui.lastQuery')
await api.storage.kv.set('ui.lastQuery', 'postgres')
await api.storage.kv.delete('ui.lastQuery')
const keys = await api.storage.kv.keys()
// ③ 自建表:SQL 自由发挥
await api.storage.db.exec('CREATE TABLE IF NOT EXISTS notes (...)') // DDL,激活期执行
await api.storage.db.run('INSERT INTO notes (id, title) VALUES (?, ?)', ['n1', '标题'])
const rows = await api.storage.db.query('SELECT * FROM notes WHERE title LIKE ?', ['%标%'])
// 原子批量:worker 端 BEGIN/COMMIT,任一失败整体回滚
await api.storage.db.batch([
{ sql: 'INSERT INTO notes (id, title) VALUES (?, ?)', params: ['n2', 'a'] },
{ sql: 'UPDATE notes SET title = ? WHERE id = ?', params: ['b', 'n2'] }
])
// ④ 自由文件(日志快照、导出等)
const dir = api.storage.dir() // userData/plugin-data/<id>/
})
```
## 3. schema 声明为什么是硬性要求
插件会被 **Agent 自我迭代**。迭代前 Agent 需要知道「库里有什么」:
- `declareSchema` 的内容随 `plugins:list` 下发——管理页「存储区」卡片与(未来的)Agent 内省工具读的是**同一份**数据;
- 每张表、每个字段的 `description` 必填,`plugin:check` 会拒绝「用了 db 却不声明 schema」的插件;
- 写给 Agent 读的 description 请具体(「创建时间,Unix 毫秒」而不是「时间」)。
## 4. 迁移(表结构变更)
宿主不解释 schema,迁移由插件自管,惯用法:
```ts
const row = await api.storage.db.query('PRAGMA user_version')
const v = Number(row[0]?.user_version ?? 0)
if (v < 1) {
await api.storage.db.exec('ALTER TABLE notes ADD COLUMN tag TEXT')
await api.storage.db.exec('PRAGMA user_version = 1')
}
```
## 5. 设置页管理
- 二级页「存储区」卡片:schema 表格化展示(表/列/类型/描述)+ **数据目录**(资源管理器打开)+ **清除数据**(二次确认,连接关闭后整目录删除)。
## 6. 边界
- 存储只在 **main 层**提供;renderer 组件 / iframe 页需要数据时,让 main 侧经状态胶囊/设置值转发(受控 IPC 通道属后续契约)。
- KV 的 value 必须可 JSON 序列化(Date/Map 请先转换);大二进制放文件,路径存 KV。
- 跨插件**不直读对方库**:需要别人的数据 = 声明依赖 + 走对方服务(见 L8)。