348 lines
14 KiB
Markdown
348 lines
14 KiB
Markdown
# NanoClaw — 中央数据库模式(Central DB Schema)
|
||
|
||
`data/v2.db` 的完整参考,这是主机拥有的管理平面数据库。先阅读 [db.md](db.md) 了解三数据库概览、结构和跨挂载规则。
|
||
|
||
访问层:`src/db/`。权威模式参考:`src/db/schema.ts`(仅注释——实际创建通过 `src/db/migrations/` 中的迁移运行)。
|
||
|
||
---
|
||
|
||
## 1. 表
|
||
|
||
### 1.1 `agent_groups`
|
||
|
||
代理工作区(workspace)。每个 1:1 映射到一个 `groups/<folder>/` 目录,该目录包含 `CLAUDE.md` 和技能。容器配置位于 `container_configs` 中(见下方 §1.x);在生成容器时会物化(materialize)一个 `container.json` 文件供容器运行器读取。
|
||
|
||
```sql
|
||
CREATE TABLE agent_groups (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
folder TEXT NOT NULL UNIQUE,
|
||
agent_provider TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
- **读取者:**`src/session-manager.ts`、`src/delivery.ts`、`src/router.ts`
|
||
- **写入者:**`src/db/agent-groups.ts`
|
||
|
||
### 1.2 `messaging_groups`
|
||
|
||
每个平台聊天一行(一个 WhatsApp 群组、一个 Slack 频道、一个 1:1 私信等)。
|
||
|
||
```sql
|
||
CREATE TABLE messaging_groups (
|
||
id TEXT PRIMARY KEY,
|
||
channel_type TEXT NOT NULL,
|
||
platform_id TEXT NOT NULL,
|
||
name TEXT,
|
||
is_group INTEGER DEFAULT 0,
|
||
unknown_sender_policy TEXT NOT NULL DEFAULT 'strict',
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(channel_type, platform_id)
|
||
);
|
||
```
|
||
|
||
- `unknown_sender_policy`:`strict`(丢弃)、`request_approval`(请求管理员)、`public`(允许)。
|
||
- **读取者:**`src/router.ts`、`src/delivery.ts`、`src/session-manager.ts`
|
||
- **写入者:**`src/db/messaging-groups.ts`、频道设置流程
|
||
|
||
### 1.3 `messaging_group_agents`
|
||
|
||
接线表(wiring):哪个代理组处理哪个消息组。多对多——同一频道可以路由到多个代理(参见 [isolation-model.md](isolation-model.md))。
|
||
|
||
```sql
|
||
CREATE TABLE messaging_group_agents (
|
||
id TEXT PRIMARY KEY,
|
||
messaging_group_id TEXT NOT NULL REFERENCES messaging_groups(id),
|
||
agent_group_id TEXT NOT NULL REFERENCES agent_groups(id),
|
||
trigger_rules TEXT,
|
||
response_scope TEXT DEFAULT 'all',
|
||
session_mode TEXT DEFAULT 'shared',
|
||
priority INTEGER DEFAULT 0,
|
||
created_at TEXT NOT NULL,
|
||
UNIQUE(messaging_group_id, agent_group_id)
|
||
);
|
||
```
|
||
|
||
- `session_mode`:`shared`(每频道一个会话)、`per-thread`(每线程一个)、`agent-shared`(每个代理组跨所有频道一个)。
|
||
- `trigger_rules`:JSON;例如原生频道的正则表达式。
|
||
- **副作用:**创建接线时也必须填充 `agent_destinations`——修改其中一个时不要忘记另一个(参见 §1.10)。
|
||
|
||
### 1.4 `users`
|
||
|
||
平台用户身份。ID 命名空间化:`tg:123456`、`discord:abc`、`phone:+1555...`、`email:a@x.com`。一个人可能拥有多行——尚无跨频道关联。
|
||
|
||
```sql
|
||
CREATE TABLE users (
|
||
id TEXT PRIMARY KEY,
|
||
kind TEXT NOT NULL,
|
||
display_name TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
- **写入者/读取者:**`src/db/users.ts`;频道认证流程
|
||
|
||
### 1.5 `user_roles`
|
||
|
||
权限表。**权限是用户级别的,永远不是代理组级别的。**
|
||
|
||
```sql
|
||
CREATE TABLE user_roles (
|
||
user_id TEXT NOT NULL REFERENCES users(id),
|
||
role TEXT NOT NULL,
|
||
agent_group_id TEXT REFERENCES agent_groups(id),
|
||
granted_by TEXT REFERENCES users(id),
|
||
granted_at TEXT NOT NULL,
|
||
PRIMARY KEY (user_id, role, agent_group_id)
|
||
);
|
||
CREATE INDEX idx_user_roles_scope ON user_roles(agent_group_id, role);
|
||
```
|
||
|
||
不变量(Invariants):
|
||
- `role = 'owner'` → 必须是全局的 (`agent_group_id IS NULL`)。在 `grantRole()` 中强制执行。
|
||
- `role = 'admin'` → 全局(NULL)或限定到一个代理组。
|
||
- 代理组 A 的 admin 隐含了 A 的成员身份——不需要 `agent_group_members` 行。
|
||
|
||
访问层:`src/db/user-roles.ts`、`src/access.ts`。
|
||
|
||
### 1.6 `agent_group_members`
|
||
|
||
非特权用户的显式成员身份。Owner 和 admin 不需要此处的行——他们是隐式成员。
|
||
|
||
```sql
|
||
CREATE TABLE agent_group_members (
|
||
user_id TEXT NOT NULL REFERENCES users(id),
|
||
agent_group_id TEXT NOT NULL REFERENCES agent_groups(id),
|
||
added_by TEXT REFERENCES users(id),
|
||
added_at TEXT NOT NULL,
|
||
PRIMARY KEY (user_id, agent_group_id)
|
||
);
|
||
```
|
||
|
||
### 1.7 `user_dms`
|
||
|
||
私信(DM)频道发现的缓存。让主机无需每次都调用平台的 `openConversation` API 即可发送冷私信(批准卡片、配对码等)。
|
||
|
||
```sql
|
||
CREATE TABLE user_dms (
|
||
user_id TEXT NOT NULL REFERENCES users(id),
|
||
channel_type TEXT NOT NULL,
|
||
messaging_group_id TEXT NOT NULL REFERENCES messaging_groups(id),
|
||
resolved_at TEXT NOT NULL,
|
||
PRIMARY KEY (user_id, channel_type)
|
||
);
|
||
```
|
||
|
||
由 `src/user-dm.ts` 中的 `ensureUserDm()` 延迟填充。
|
||
|
||
### 1.8 `sessions`
|
||
|
||
会话注册表(session registry)。每个受 `session_mode` 约束的(代理组、消息组、线程)元组一行。仅存储生命周期元数据——不含消息。
|
||
|
||
```sql
|
||
CREATE TABLE sessions (
|
||
id TEXT PRIMARY KEY,
|
||
agent_group_id TEXT NOT NULL REFERENCES agent_groups(id),
|
||
messaging_group_id TEXT REFERENCES messaging_groups(id),
|
||
thread_id TEXT,
|
||
agent_provider TEXT,
|
||
status TEXT DEFAULT 'active',
|
||
container_status TEXT DEFAULT 'stopped',
|
||
last_active TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
CREATE INDEX idx_sessions_agent_group ON sessions(agent_group_id);
|
||
CREATE INDEX idx_sessions_lookup ON sessions(messaging_group_id, thread_id);
|
||
```
|
||
|
||
- **由 `resolveSession()` 解析:**`src/session-manager.ts`。
|
||
- 创建会话还会通过 `initSessionFolder()` 配置会话文件夹和两个会话数据库——参见 [db-session.md](db-session.md)。
|
||
|
||
### 1.9 `pending_questions`
|
||
|
||
`ask_user_question` MCP 工具将一个交互式问题停放在此处,容器通过 `questionId` 将传入的 `system` 消息匹配回来。
|
||
|
||
```sql
|
||
CREATE TABLE pending_questions (
|
||
question_id TEXT PRIMARY KEY,
|
||
session_id TEXT NOT NULL REFERENCES sessions(id),
|
||
message_out_id TEXT NOT NULL,
|
||
platform_id TEXT,
|
||
channel_type TEXT,
|
||
thread_id TEXT,
|
||
title TEXT NOT NULL,
|
||
options_json TEXT NOT NULL,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### 1.10 `agent_destinations`
|
||
|
||
用于出站发送的权限 ACL 和名称解析映射。代理请求 `send_message(to="dev-channel")` 必须在此有一个 `local_name = 'dev-channel'` 的行,否则发送将被拒绝为 `unknown destination`。
|
||
|
||
```sql
|
||
CREATE TABLE agent_destinations (
|
||
agent_group_id TEXT NOT NULL REFERENCES agent_groups(id),
|
||
local_name TEXT NOT NULL,
|
||
target_type TEXT NOT NULL, -- 'channel' | 'agent'
|
||
target_id TEXT NOT NULL, -- messaging_group_id | agent_group_id
|
||
created_at TEXT NOT NULL,
|
||
PRIMARY KEY (agent_group_id, local_name)
|
||
);
|
||
CREATE INDEX idx_agent_dest_target ON agent_destinations(target_type, target_id);
|
||
```
|
||
|
||
**投影不变量(负载关键)。**中央表是真实数据源,但每个运行中的容器从其自己的 `inbound.db` 读取投影(参见 [db-session.md §2.3](db-session.md#23-destinations))。任何在容器运行时修改 `agent_destinations` 的代码还必须调用 `writeDestinations()`(`src/session-manager.ts`),否则容器将因过时数据拒绝发送。已知调用点:`createMessagingGroupAgent()` 在 `src/db/messaging-groups.ts` 中,`create_agent` 系统动作在 `src/delivery.ts` 中。
|
||
|
||
访问层:`src/db/agent-destinations.ts`。
|
||
|
||
### 1.11 `pending_approvals`
|
||
|
||
两个工作流共享此表:
|
||
|
||
- **会话绑定的 MCP 批准**——`install_packages`、`add_mcp_server`。`session_id` 有值。
|
||
- **OneCLI 凭证批准**——`session_id` 可为 NULL;`agent_group_id` + `channel_type` + `platform_id` 路由管理卡片。
|
||
|
||
```sql
|
||
CREATE TABLE pending_approvals (
|
||
approval_id TEXT PRIMARY KEY,
|
||
session_id TEXT REFERENCES sessions(id),
|
||
request_id TEXT NOT NULL,
|
||
action TEXT NOT NULL,
|
||
payload TEXT NOT NULL,
|
||
created_at TEXT NOT NULL,
|
||
agent_group_id TEXT REFERENCES agent_groups(id),
|
||
channel_type TEXT,
|
||
platform_id TEXT,
|
||
platform_message_id TEXT,
|
||
expires_at TEXT,
|
||
status TEXT NOT NULL DEFAULT 'pending',
|
||
title TEXT NOT NULL DEFAULT '',
|
||
options_json TEXT NOT NULL DEFAULT '[]'
|
||
);
|
||
CREATE INDEX idx_pending_approvals_action_status ON pending_approvals(action, status);
|
||
```
|
||
|
||
- `status`:`pending` | `approved` | `rejected` | `expired`。
|
||
- `platform_message_id` 让主机在决策后原地编辑管理卡片。
|
||
- 访问层:`src/db/sessions.ts`;清理和递送:`src/onecli-approvals.ts`。
|
||
|
||
### 1.12 `unregistered_senders`
|
||
|
||
审计追踪:每次消息被丢弃(未知发送者、严格策略),我们在此递增计数器,以便管理员可以看到谁一直在尝试联系。
|
||
|
||
```sql
|
||
CREATE TABLE unregistered_senders (
|
||
channel_type TEXT NOT NULL,
|
||
platform_id TEXT NOT NULL,
|
||
user_id TEXT,
|
||
sender_name TEXT,
|
||
reason TEXT NOT NULL,
|
||
messaging_group_id TEXT,
|
||
agent_group_id TEXT,
|
||
message_count INTEGER NOT NULL DEFAULT 1,
|
||
first_seen TEXT NOT NULL,
|
||
last_seen TEXT NOT NULL,
|
||
PRIMARY KEY (channel_type, platform_id)
|
||
);
|
||
CREATE INDEX idx_unregistered_senders_last_seen ON unregistered_senders(last_seen);
|
||
```
|
||
|
||
写入者:`recordDroppedMessage()` 在 `src/db/dropped-messages.ts` 中。冲突时递增 `message_count` + `last_seen`。
|
||
|
||
### 1.13 Chat SDK 桥接表
|
||
|
||
支持 Chat SDK 桥接使用的 `SqliteStateAdapter` 的状态(参见 [api-details.md](api-details.md))。NanoClaw 代码很少直接接触这些表——它们由 `src/state-sqlite.ts` 拥有。
|
||
|
||
```sql
|
||
CREATE TABLE chat_sdk_kv (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT NOT NULL,
|
||
expires_at INTEGER -- unix 时间戳,可为空
|
||
);
|
||
|
||
CREATE TABLE chat_sdk_subscriptions (
|
||
thread_id TEXT PRIMARY KEY,
|
||
subscribed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE chat_sdk_locks (
|
||
thread_id TEXT PRIMARY KEY,
|
||
token TEXT NOT NULL,
|
||
expires_at INTEGER NOT NULL
|
||
);
|
||
|
||
CREATE TABLE chat_sdk_lists (
|
||
key TEXT NOT NULL,
|
||
idx INTEGER NOT NULL,
|
||
value TEXT NOT NULL,
|
||
expires_at INTEGER,
|
||
PRIMARY KEY (key, idx)
|
||
);
|
||
```
|
||
|
||
### 1.14 `schema_version`
|
||
|
||
迁移账本(migration ledger),由迁移运行器(§2)写入。
|
||
|
||
```sql
|
||
CREATE TABLE schema_version (
|
||
version INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
applied TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### 1.15 `container_configs`
|
||
|
||
每个代理组的容器运行时配置。`provider`、`model`、`packages`、MCP 服务器、挂载、CLI 范围等的真实数据源。在生成容器时物化到 `groups/<folder>/container.json`。
|
||
|
||
```sql
|
||
CREATE TABLE container_configs (
|
||
agent_group_id TEXT PRIMARY KEY REFERENCES agent_groups(id) ON DELETE CASCADE,
|
||
provider TEXT,
|
||
model TEXT,
|
||
effort TEXT,
|
||
image_tag TEXT,
|
||
assistant_name TEXT,
|
||
max_messages_per_prompt INTEGER,
|
||
skills TEXT NOT NULL DEFAULT '"all"',
|
||
mcp_servers TEXT NOT NULL DEFAULT '{}',
|
||
packages_apt TEXT NOT NULL DEFAULT '[]',
|
||
packages_npm TEXT NOT NULL DEFAULT '[]',
|
||
additional_mounts TEXT NOT NULL DEFAULT '[]',
|
||
cli_scope TEXT NOT NULL DEFAULT 'group', -- disabled | group | global
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
- **读取者:**`src/container-config.ts`、`src/container-runner.ts`、`src/cli/dispatch.ts`(范围强制执行)、`src/claude-md-compose.ts`
|
||
- **写入者:**`src/db/container-configs.ts`、`src/modules/self-mod/apply.ts`、`src/backfill-container-configs.ts`
|
||
|
||
---
|
||
|
||
## 2. 迁移系统
|
||
|
||
迁移(migrations)位于 `src/db/migrations/`,每个迁移一个文件。运行器:`runMigrations()` 在 `src/db/migrations/index.ts` 中。它:
|
||
|
||
1. 如果不存在则创建 `schema_version`。
|
||
2. 读取 `MAX(version)`——称为 `current`。
|
||
3. 对每个 `version > current` 的迁移,在事务内执行 `up(db)` 并追加 `schema_version` 行。
|
||
|
||
| # | 文件 | 引入内容 |
|
||
|---|------|------------|
|
||
| 001 | `001-initial.ts` | 核心表:`agent_groups`、`messaging_groups`、`messaging_group_agents`、`users`、`user_roles`、`agent_group_members`、`user_dms`、`sessions`、`pending_questions` |
|
||
| 002 | `002-chat-sdk-state.ts` | `chat_sdk_kv`、`chat_sdk_subscriptions`、`chat_sdk_locks`、`chat_sdk_lists` |
|
||
| 003 | `003-pending-approvals.ts` | `pending_approvals`(会话绑定 + OneCLI 字段) |
|
||
| 004 | `004-agent-destinations.ts` | `agent_destinations` + 从现有 `messaging_group_agents` 接线回填 |
|
||
| 007 | `007-pending-approvals-title-options.ts` | `ALTER TABLE pending_approvals` 添加 `title`、`options_json`(改造在 003 和 007 之间创建的数据库) |
|
||
| 008 | `008-dropped-messages.ts` | `unregistered_senders` |
|
||
| 009 | `009-drop-pending-credentials.ts` | 删除已废弃的 `pending_credentials` 表 |
|
||
| 014 | `014-container-configs.ts` | `container_configs`——每个代理组的容器运行时配置 |
|
||
| 015 | `015-cli-scope.ts` | `ALTER TABLE container_configs ADD COLUMN cli_scope` |
|
||
|
||
编号 005 和 006 有意空缺——迁移在早期开发期间重新编号。
|
||
|
||
会话数据库模式(`INBOUND_SCHEMA`、`OUTBOUND_SCHEMA`)**不**在此处进行版本管理。它们使用 `CREATE TABLE IF NOT EXISTS`,因此当重新打开旧版本构建的会话文件时,新列通过会话数据库的延迟迁移辅助函数(`migrateDeliveredTable()` 等)添加。参见 [db-session.md](db-session.md)。
|