Files
nanoclaw/docs/zh/db-session.md
2026-05-12 13:14:17 +00:00

187 lines
8.4 KiB
Markdown
Raw Permalink 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.

# NanoClaw — 每个 Session 的 DB Schema
每个 session会话拥有的两个 SQLite 文件的参考:`inbound.db`(宿主机写入,容器读取)和 `outbound.db`(容器写入,宿主机读取)。请先阅读 [db.md](db.md) 了解三库概览、单一写入者规则以及跨挂载可见性约束。
Schema 位于 `src/db/schema.ts` 中,作为 `INBOUND_SCHEMA``OUTBOUND_SCHEMA` 常量。当新的 session 目录被配置时,两个文件都由 `src/session-manager.ts` 中的 `ensureSchema()` 创建。
---
## 1. Session 目录布局
```
data/v2-sessions/<agent_group_id>/<session_id>/
inbound.db ← 宿主机写入,容器读取(只读挂载)
outbound.db ← 容器写入,宿主机读取(只读打开)
.heartbeat ← 容器触碰的 mtime非 DB 写入)
inbox/<message_id>/ ← 用户附件,从入站消息内容解码
outbox/<message_id>/ ← agent 生成的附件
```
一个 session = 一个目录 = 一对 DB。`agent_group_id` 父目录还存放每个 agent group 共享的状态(`.claude-shared/``agent-runner-src/`),这些状态在该 agent group 的所有 session 间共享。
`src/session-manager.ts` 中的路径辅助函数:`sessionDir()``inboundDbPath()``outboundDbPath()``heartbeatPath()`
---
## 2. 入站库 (`inbound.db`)
宿主机拥有容器只读。Schema 常量:`src/db/schema.ts` 中的 `INBOUND_SCHEMA`
### 2.1 `messages_in`
抵达 session 的每条消息:用户聊天、计划任务、重复任务、问题回复、内部系统消息。
```sql
CREATE TABLE messages_in (
id TEXT PRIMARY KEY,
seq INTEGER UNIQUE, -- 仅偶数(宿主机分配)——见 §3
kind TEXT NOT NULL,
timestamp TEXT NOT NULL,
status TEXT DEFAULT 'pending', -- pending|completed|failed|paused
process_after TEXT,
recurrence TEXT, -- 重复任务的 cron 表达式
series_id TEXT, -- 将重复任务的发生次数分组
tries INTEGER DEFAULT 0,
trigger INTEGER NOT NULL DEFAULT 1, -- 0 = 仅上下文不唤醒1 = 唤醒 agent
platform_id TEXT,
channel_type TEXT,
thread_id TEXT,
content TEXT NOT NULL, -- JSON格式取决于 kind
source_session_id TEXT, -- agent 到 agent 的返回路径
on_wake INTEGER NOT NULL DEFAULT 0 -- 1 = 仅在容器的首次轮询时投递
);
CREATE INDEX idx_messages_in_series ON messages_in(series_id);
```
内容格式:见 [api-details.md §Session DB Schema Details](api-details.md#session-db-schema-details)。
**写入者(宿主机):** `insertMessage()``insertTask()``insertRecurrence()`——均在 `src/db/session-db.ts` 中。每个都调用 `nextEvenSeq()`
**读取者(容器):** `container/agent-runner/src/db/messages-in.ts`——轮询 `status='pending' AND (process_after IS NULL OR process_after <= now)`
### 2.2 `delivered`
宿主机在将 `messages_out` 行交给频道适配器后写入此处。容器读取 `platform_message_id` 以定位编辑和反应。
```sql
CREATE TABLE delivered (
message_out_id TEXT PRIMARY KEY,
platform_message_id TEXT,
status TEXT NOT NULL DEFAULT 'delivered', -- delivered|failed
delivered_at TEXT NOT NULL
);
```
写入者:`src/db/session-db.ts` 中的 `markDelivered()` / `markDeliveryFailed()`。较旧的 session DB 由 `migrateDeliveredTable()` 惰性地升级 schema。
### 2.3 `destinations`
本 session 的 agent 对应的中央 `agent_destinations` 表(见 [db-central.md §1.10](db-central.md#110-agent_destinations))的投影。容器根据此表解析 `to="name"`;如果行不存在,则拒绝发送并报 `unknown destination`
```sql
CREATE TABLE destinations (
name TEXT PRIMARY KEY,
display_name TEXT,
type TEXT NOT NULL, -- 'channel' | 'agent'
channel_type TEXT, -- 用于 type='channel'
platform_id TEXT, -- 用于 type='channel'
agent_group_id TEXT -- 用于 type='agent'
);
```
在每次容器唤醒时以及连接配置在 session 中途变更时,由 `writeDestinations()` 整体重写(事务中的 DELETE + INSERT`src/db/schema.ts` 中该表的注释是刷新语义的规范陈述。
### 2.4 `session_routing`
单行(`id=1`)默认路由:当 agent 未指定目的地时,出站消息的走向。
```sql
CREATE TABLE session_routing (
id INTEGER PRIMARY KEY CHECK (id = 1),
channel_type TEXT,
platform_id TEXT,
thread_id TEXT
);
```
`writeSessionRouting()` 在每次容器唤醒时写入,数据来源于 `sessions.messaging_group_id` + `sessions.thread_id`
---
## 3. 序列号奇偶规则
每条消息(入站或出站)获得一个单调递增的整数 `seq`,在 session 内跨两张表唯一。
- **宿主机写入偶数 seq**2、4、6、…`messages_in`——`src/db/session-db.ts:75` 中的 `nextEvenSeq()`
- **容器写入奇数 seq**1、3、5、…`messages_out`——逻辑在 `container/agent-runner/src/db/messages-out.ts:54``max % 2 === 0 ? max + 1 : max + 2`),跨*两张*表读取 `MAX(seq)` 以保持全局顺序。
为什么不相交?`seq` 是 agent 视角的消息 ID。当 agent 调用 `edit_message(seq=5)``add_reaction(seq=6)` 时,`getMessageIdBySeq()` 利用奇偶来路由查找:奇数 → `messages_out`,偶数 → `messages_in`。单凭奇偶就能消除歧义,无需连接。冲突会破坏编辑功能。
如果你添加了一条写入任一张表的代码路径,请保持奇偶规则——该规则不是通过约束强制执行的,只有两个辅助函数在维护。
---
## 4. 出站库 (`outbound.db`)
容器拥有宿主机只读。Schema 常量:`src/db/schema.ts` 中的 `OUTBOUND_SCHEMA`
### 4.1 `messages_out`
agent 生成的所有内容聊天回复、编辑、反应、卡片、问题发送、agent 到 agent 消息、系统动作。
```sql
CREATE TABLE messages_out (
id TEXT PRIMARY KEY,
seq INTEGER UNIQUE, -- 仅奇数(容器分配)——见 §3
in_reply_to TEXT,
timestamp TEXT NOT NULL,
deliver_after TEXT,
recurrence TEXT,
kind TEXT NOT NULL, -- chat|chat-sdk|system|…
platform_id TEXT,
channel_type TEXT,
thread_id TEXT,
content TEXT NOT NULL -- JSON操作类型包含在其中edit/reaction/card/…)
);
```
内容格式:见 [api-details.md §Session DB Schema Details](api-details.md#session-db-schema-details)。
**写入者(容器):** `container/agent-runner/src/db/messages-out.ts` 中的 `writeMessageOut()`
**读取者(宿主机):** `src/delivery.ts`(轮询投递),`getMessageIdBySeq()` / `getRoutingBySeq()` 用于编辑/反应定位。
### 4.2 `processing_ack`
容器侧对它所接触的每个 `messages_in.id` 的状态记录。宿主机轮询此表并将状态同步回 `messages_in`——这避免了容器向 `inbound.db` 写入。
```sql
CREATE TABLE processing_ack (
message_id TEXT PRIMARY KEY,
status TEXT NOT NULL, -- processing|completed|failed
status_changed TEXT NOT NULL
);
```
崩溃恢复:容器启动时,陈旧的 `processing` 条目被清除。宿主机侧同步:`src/host-sweep.ts` 中的 `syncProcessingAcks()`
### 4.3 `session_state`
持久化的容器拥有的 KV 存储。主要消费者是 Chat SDK session ID——将其存储在这里可以让 agent 的对话在容器重启后恢复。可通过 `/clear` 清除。
```sql
CREATE TABLE session_state (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
访问方式:`container/agent-runner/src/db/session-state.ts`
---
## 5. Schema 演化
与中央库不同session DB **不**经过编号迁移。`INBOUND_SCHEMA``OUTBOUND_SCHEMA` 都使用 `CREATE TABLE IF NOT EXISTS`,因此新的 session 总是获得最新的 schema。对于在较旧版本下创建的 session 目录,列级别的差异在打开时惰性修补——例如 `src/db/session-db.ts` 中的 `migrateDeliveredTable()` 如果缺少 `platform_message_id``status` 列,则会将其添加到 `delivered` 表。
如果你向任一 schema 添加了一个列,请为现有的 session 目录添加匹配的惰性迁移,并优先使用可空的列或带默认值的列,这样就不需要回填数据。