216 lines
6.7 KiB
Markdown
216 lines
6.7 KiB
Markdown
# NanoClaw 架构图
|
||
|
||
## 系统概览
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph Platforms["消息平台"]
|
||
P1[Discord]
|
||
P2[Telegram]
|
||
P3[Slack]
|
||
P4[GitHub / Linear]
|
||
P5[WhatsApp / iMessage / Teams / GChat / Matrix / Webex / Email]
|
||
end
|
||
|
||
subgraph Host["宿主机进程 (Node)"]
|
||
direction TB
|
||
Bridge["Chat SDK 桥接<br/>(src/channels/chat-sdk-bridge.ts)"]
|
||
Router["路由器<br/>(src/router.ts)<br/>platformId + threadId -> messaging_group -> agent_group -> session"]
|
||
SessMgr["Session 管理器<br/>(src/session-manager.ts)<br/>创建 inbound.db + outbound.db"]
|
||
Runner["容器运行器<br/>(src/container-runner.ts)<br/>OneCLI ensureAgent + 启动"]
|
||
Delivery["投递轮询器<br/>(src/delivery.ts)<br/>活跃时 1s / sweep 时 60s"]
|
||
Sweep["宿主机 Sweep<br/>(src/host-sweep.ts)<br/>心跳、重试、重复执行"]
|
||
Central[("中央库<br/>data/v2.db<br/>agent_groups<br/>messaging_groups<br/>messaging_group_agents<br/>sessions<br/>pending_approvals")]
|
||
end
|
||
|
||
subgraph OneCLI["OneCLI 网关 (0.3.1)"]
|
||
Vault["Agent Vault<br/>秘密 + OAuth"]
|
||
Approvals["configureManualApproval<br/>-> pending_approvals"]
|
||
end
|
||
|
||
subgraph Session["每个 Session 的容器 (Docker / Apple Container)"]
|
||
direction TB
|
||
PollLoop["轮询循环<br/>(container/agent-runner)"]
|
||
Provider["Agent 提供程序<br/>(claude、opencode、mock;待办: codex)"]
|
||
MCP["MCP 工具<br/>send_message, send_file, edit_message,<br/>add_reaction, send_card, ask_user_question,<br/>schedule_task, create_agent,<br/>install_packages, add_mcp_server"]
|
||
Skills["容器技能<br/>(container/skills/)"]
|
||
InDB[("inbound.db<br/>宿主机写入<br/>偶数 seq<br/>messages_in<br/>destinations<br/>processing_ack")]
|
||
OutDB[("outbound.db<br/>容器写入<br/>奇数 seq<br/>messages_out<br/>心跳文件")]
|
||
end
|
||
|
||
subgraph Groups["Agent Group 文件系统 (groups/*)"]
|
||
Folder["CLAUDE.md<br/>记忆<br/>每个 group 的技能<br/>container_config"]
|
||
end
|
||
|
||
P1 & P2 & P3 & P4 & P5 --> Bridge
|
||
Bridge --> Router
|
||
Router --> Central
|
||
Router --> SessMgr
|
||
SessMgr --> InDB
|
||
SessMgr --> Runner
|
||
Runner --> OneCLI
|
||
Runner --> PollLoop
|
||
PollLoop --> InDB
|
||
PollLoop --> Provider
|
||
Provider --> MCP
|
||
Provider --> Skills
|
||
MCP --> OutDB
|
||
OutDB --> Delivery
|
||
Delivery --> Central
|
||
Delivery --> Bridge
|
||
Bridge --> P1 & P2 & P3 & P4 & P5
|
||
Sweep --> InDB
|
||
Sweep --> OutDB
|
||
Sweep --> Central
|
||
Runner -.挂载.-> Folder
|
||
MCP -.审批.-> Approvals
|
||
Approvals --> Central
|
||
Provider -.API 调用.-> Vault
|
||
```
|
||
|
||
## 消息流程 (入站 -> agent -> 出站)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant P as 平台 (例如 Telegram)
|
||
participant B as Chat SDK 桥接
|
||
participant R as 路由器
|
||
participant SM as Session 管理器
|
||
participant IDB as inbound.db
|
||
participant C as 容器 (agent-runner)
|
||
participant ODB as outbound.db
|
||
participant D as 投递轮询器
|
||
|
||
P->>B: 新消息
|
||
B->>R: routeInbound(platformId, threadId, msg)
|
||
R->>R: 解析 messaging_group -> agent_group -> session<br/>(agent-shared | shared | per-thread)
|
||
R->>SM: 确保 session + DB 存在
|
||
R->>IDB: INSERT messages_in (偶数 seq)
|
||
R->>C: 唤醒容器 (docker run / 已在运行)
|
||
C->>IDB: 轮询 messages_in
|
||
C->>C: 格式化 xml, 流式传输到选定的 provider
|
||
C->>ODB: INSERT messages_out (奇数 seq)<br/>解析 <message to="name"> 块
|
||
D->>ODB: 1s 轮询(活跃)/ 60s(sweep)
|
||
D->>D: hasDestination() 重新验证
|
||
D->>B: 通过适配器投递
|
||
B->>P: 发送消息 / 编辑 / 反应 / 文件 / 卡片
|
||
```
|
||
|
||
## 命名目的地 + Agent 到 Agent
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph AgentA["Agent Group A (主agent)"]
|
||
A_out["输出:<br/><message to='slack'>...</message><br/><message to='browser-agent'>...</message><br/><internal>scratchpad</internal>"]
|
||
end
|
||
|
||
subgraph Dests["inbound.db.destinations (每个 agent)"]
|
||
D1["slack -> messaging_group 42"]
|
||
D2["browser-agent -> agent_group 7<br/>(双向行)"]
|
||
D3["github -> messaging_group 13"]
|
||
end
|
||
|
||
subgraph AgentB["Agent Group B (浏览器子agent)"]
|
||
B_session["自己的 inbound.db / outbound.db<br/>继承了返回到 A 的目的地"]
|
||
end
|
||
|
||
Slack[Slack 频道]
|
||
GitHub[GitHub PR 线程]
|
||
|
||
A_out -->|解析 + 查找| Dests
|
||
D1 -->|投递| Slack
|
||
D2 -->|写入 B 的 inbound.db| B_session
|
||
D3 -->|投递| GitHub
|
||
B_session -.通过 'parent' 回复.-> Dests
|
||
```
|
||
|
||
## 实体模型 + 隔离级别
|
||
|
||
```mermaid
|
||
erDiagram
|
||
agent_groups ||--o{ messaging_group_agents : 已连接
|
||
messaging_groups ||--o{ messaging_group_agents : 已连接
|
||
agent_groups ||--o{ sessions : 运行
|
||
messaging_groups ||--o{ sessions : 上下文
|
||
agent_groups ||--o{ agent_destinations : 拥有
|
||
agent_groups ||--o{ pending_approvals : 请求
|
||
|
||
agent_groups {
|
||
int id
|
||
string name
|
||
string folder
|
||
string agent_provider
|
||
json container_config
|
||
}
|
||
messaging_groups {
|
||
int id
|
||
string channel_type
|
||
string platform_id
|
||
string name
|
||
bool is_group
|
||
string unknown_sender_policy "strict | request_approval | public"
|
||
}
|
||
users {
|
||
string id PK "命名空间 <channel>:<handle>"
|
||
string kind
|
||
string display_name
|
||
}
|
||
user_roles {
|
||
string user_id FK
|
||
string role "owner | admin"
|
||
string agent_group_id FK "null = 全局"
|
||
}
|
||
agent_group_members {
|
||
string user_id FK
|
||
string agent_group_id FK
|
||
}
|
||
user_dms {
|
||
string user_id FK
|
||
string channel_type
|
||
string messaging_group_id FK
|
||
}
|
||
messaging_group_agents {
|
||
int messaging_group_id
|
||
int agent_group_id
|
||
string session_mode "agent-shared | shared | per-thread"
|
||
json trigger_rules
|
||
int priority
|
||
}
|
||
sessions {
|
||
int id
|
||
int agent_group_id
|
||
int messaging_group_id
|
||
string sdk_session_id
|
||
string status
|
||
}
|
||
```
|
||
|
||
### 隔离级别速查表
|
||
|
||
| 级别 | `session_mode` | 共享内容 | 示例 |
|
||
|---|---|---|---|
|
||
| 1. 共享 session | `agent-shared` | 工作区 + 记忆 + 对话 | Slack + GitHub webhooks 在同一线程 |
|
||
| 2. 相同 agent,独立 session | `shared` / `per-thread` | 仅工作区 + 记忆 | 一个 agent 跨 3 个 Telegram 聊天 |
|
||
| 3. 独立的 agent group | (不同的 `agent_group_id`) | 无 | 个人 vs 工作频道 |
|
||
|
||
## 双库拆分(为什么)
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Mount["/workspace (挂载到容器中的卷)"]
|
||
In[("inbound.db")]
|
||
Out[("outbound.db")]
|
||
HB["/.heartbeat (文件 touch)"]
|
||
end
|
||
|
||
Host[宿主机进程] -->|"仅写入<br/>(偶数 seq)"| In
|
||
Host -->|读取| Out
|
||
Container[agent-runner] -->|读取| In
|
||
Container -->|"仅写入<br/>(奇数 seq)"| Out
|
||
Container -->|每次轮询 touch| HB
|
||
HostSweep[宿主机 sweep] -->|stat mtime| HB
|
||
HostSweep -->|读取 processing_ack| In
|
||
|
||
note1["每个文件有且仅有一个写入者。<br/>消除了 SQLite 跨进程写入竞争。<br/>无冲突的 seq 编号。"]
|
||
```
|