添加中文文档

This commit is contained in:
2026-05-12 13:14:17 +00:00
parent 61d7ca6bba
commit 38bb076ac6
24 changed files with 6876 additions and 0 deletions

186
docs/zh/db-session.md Normal file
View File

@@ -0,0 +1,186 @@
# 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 目录添加匹配的惰性迁移,并优先使用可空的列或带默认值的列,这样就不需要回填数据。