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

348 lines
14 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.

# 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)。