AnyBot/docs/architecture.md

130 lines
11 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.

# 技術架構
AnyBot 是一個 Rust 程式(`anybot`),用 egui/eframe 畫介面。同一個執行檔有三種跑法:
| 跑法 | 指令 | 用途 |
|---|---|---|
| 視窗 | `anybot` | 桌面 App |
| 無視窗 | `anybot --serve` | 伺服器、一直開著的電腦、手腳:介面邏輯照跑,只是不畫畫面 |
| 設定 | `anybot setup …` | 終端機裡把這台接到帳號(`src/setup.rs`) |
另一個小程式 `anybot-relay`(`relay/`)是帳號伺服器。
## 程式分層
```
┌─────────────── 介面(src/app.rs、src/app/*)───────────────┐
│ 聊天、側邊欄、設定、電腦、檔案預覽、大腦同步(mirror_ui) │
│ 每個畫面一個 App 方法;狀態全部在 App.state(model::State) │
└───────┬───────────────────────────────┬────────────────────┘
│ 送出訊息 │ 手機/其他 AnyBot 的要求
┌───────▼──────────┐ ┌────────▼───────────────────┐
│ 回合(agent.rs) │ │ 遠端(remote.rs) │
│ 呼叫模型、跑工具、 │ │ 手機 API、裝置連結、大腦同步 │
│ 等你核准、整理摘要 │ │ 長連線(tunnel.rs、wire.rs) │
└───┬─────────┬────┘ └──────────────────────────────┘
│ │
┌───▼───┐ ┌───▼──────────────────────────────────────────────┐
│ 模型 │ │ 工具(tools.rs 為主) │
│ provi- │ │ 檔案與指令、Bot 的電腦(cloudbox.rs)、瀏覽器 │
│ ders.rs│ │ (botbrowser.rs、browser.rs、web.rs)、螢幕操作 │
└────────┘ │ (computer.rs、desktop.rs、axread.rs;Linux:x11.rs、 │
│ wl.rs、portal.rs、desk.rs)、Mac App(macapps.rs)、 │
│ 保險庫(vault*.rs)、MCP(mcp.rs)、記憶(memory.rs) │
└──────────────────────────────────────────────────────┘
```
| 檔案 | 做什麼 |
|---|---|
| `model.rs` | 資料:State、Bot、Message、設定;讀寫 `state.json` |
| `agent.rs` | 一個回合:組系統指示、送模型、收串流、跑工具、核准關卡、長對話摘要 |
| `providers.rs` | 各家模型的 API(OpenAI 相容、Anthropic、OpenCode Go…)、串流、重試、看不到圖時的看圖小幫手 |
| `tools.rs` | 檔案、指令、背景指令;指令在 macOS 沙盒裡跑(見「安全」) |
| `policy.rs` | 哪些動作直接做、先問、拒絕 |
| `cloudbox.rs`、`box/` | Bot 自己的電腦:一個 Docker 容器(有桌面、瀏覽器) |
| `computer.rs`、`desktop.rs`、`axread.rs` | 看畫面、滑鼠鍵盤;讀 App 的按鈕清單(agent-desktop,只讀) |
| `vnc.rs`、`mobile/screen.html` | 人看 Bot 的電腦、需要時接手:VNC 代理(經帳號伺服器或同一個網路),noVNC 網頁 |
| `botbrowser.rs`、`browser.rs`、`web.rs` | AnyBot 自己的瀏覽器(Chrome DevTools 協定)、讀你的瀏覽器、抓網頁 |
| `routines.rs`、`triggers.rs` | 例行任務和觸發 |
| `channels.rs` | 訊息管道(Slack、Discord、Telegram) |
| `remote.rs` | 手機 API、帳號登入、裝置連結、大腦同步的請求端 |
| `tunnel.rs`、`wire.rs` | 和帳號伺服器之間的長連線(WebSocket) |
| `app/mirror_ui.rs` | 大腦和螢幕之間的同步 |
| `setup.rs` | `anybot setup` |
| `httpd.rs` | 很小的 HTTP 伺服器(AnyBot 和帳號伺服器共用) |
| `mobile/index.html` | 手機網頁(單一檔案,編進程式裡) |
## 資料存放
都在資料資料夾裡(macOS `~/Library/Application Support/AnyBot/`、Linux `~/.local/share/AnyBot/`、Windows `%APPDATA%\AnyBot\`;`ANYBOT_DATA_DIR` 可以改):
| 檔案 | 內容 |
|---|---|
| `state.json` | 全部的 Bot、聊天、設定和金鑰 |
| `attachments/bot-<id>/` | 聊天附件 |
| `browser/` | AnyBot 瀏覽器的設定檔(登入狀態) |
| `brain-copy.json` | 螢幕電腦保留的大腦副本(離線時還能看) |
| `brain-files/` | 螢幕電腦從大腦下載的附件 |
| `image-notes.json` | 看圖小幫手對每張圖的描述(不用重看) |
| `oplog/bot-<id>.jsonl` | 每個 Bot 的工作紀錄:回合開始、每次問模型、每個步驟開始和結束、做事時傳的訊息。每行先寫進磁碟才做那件事;AnyBot 在做事時被關掉,下次打開靠它把聊天修好,並留一張「繼續」卡片(`src/oplog.rs`)。回合結束就清空 |
| `anybot.lock` | 同一個資料夾一次只給一個 AnyBot 用(視窗、`--serve`、`--ask`、`anybot setup` 寫設定時)。是系統的檔案鎖,程式結束(包括當掉)就放開(`src/lock.rs`) |
寫入時先寫暫存檔再改名,不會寫一半。
## 安全
- **核准關卡**:工具動作照規則直接做、先問或拒絕(`policy.rs`,FEATURES.md 第 16、19 節)。「直接操作模式」可以跳過詢問。
- **指令的沙盒(macOS)**:Bot 執行的指令在 `sandbox-exec` 裡跑,讀不到也寫不到 AnyBot 的資料資料夾、`.gnupg`、鑰匙圈、`.aws` 等;只有 `attachments/` 和 `.ssh` 可以讀(都不能寫,`.ssh` 可讀讓 git 和 ssh 連得動)。只有沙盒管得到的才是祕密防線,核准層不再逐字掃路徑。環境變數裡的模型金鑰會先拿掉,PATH 會補齊 Homebrew 等位置(從 Finder 或 launchd 啟動也要找得到指令)。
- **Bot 的電腦**:放在容器裡,和你的電腦分開。標準(runc)容器和主機共用 Linux 核心,Chromium 在裡面關掉自己的沙盒,容器就是唯一那道牆。Docker 主機裝了 gVisor 時,設定 › 電腦 › 容器隔離可改「強隔離(gVisor)」:容器有自己的虛擬核心,Chromium 的沙盒重新開啟,牆變成兩道(虛擬核心+瀏覽器沙盒)。改完要按電腦的「更新」重建才生效。在 macOS 上沒有能裝 gVisor 的 Docker,用 `deploy/mac/gvisor-vm.sh` 建一台自帶 Docker+gVisor 的 Lima 虛擬機,再把 Docker 主機指過去;那台虛擬機沒開時(例如 Mac 重開機後)AnyBot 會自己開。
- **保險庫**:密碼、卡號、CVC 不會交給模型,由 AnyBot 直接填進網頁。
- **多台電腦**:金鑰只留在大腦(下面說明);手腳只接受帳號裡的 AnyBot,用配對碼加入的只接受那一個大腦。
## 多台電腦怎麼運作
### 連線
每台 AnyBot(大腦、螢幕、手腳)都保持一條 WebSocket 連到帳號伺服器(`/relay/host/ws`),雙方每 15–20 秒互相確認,45–60 秒沒聲音就重連。帳號伺服器沒有 WebSocket 時(舊版),改用 HTTP 長輪詢(`/relay/host/next`)。
- 手機的要求:手機 → 帳號伺服器(HTTPS)→ 這條連線 → AnyBot → 回覆原路回去。
- AnyBot 找另一台 AnyBot(同步、手腳):從自己的連線送 `call` 上去,伺服器轉給對方的連線,答案以 `ret` 回來。
- 連線斷掉時,等待中的請求立刻收到「斷線了」,不用等逾時;伺服器也立刻知道這台離線。
- 超過 1 KB、而且壓得小的內容用 deflate 壓縮(圖片、PDF 這類已壓縮的照原樣送)。
格式見 [protocol.md](protocol.md)。
### 大腦和螢幕的同步(`app/mirror_ui.rs`)
大腦回答「從版本 N 之後改了什麼」:
- **設定**:State 去掉聊天、拿掉金鑰後算指紋,變了才送整份。
- **聊天**:每則訊息有指紋,只送第一個不同的那則之後的部分。
- **討論串**、**誰在工作**、**等你核准的事**。
螢幕的請求會等最多 20 秒,有變化就馬上回(長輪詢,跑在長連線上)。大腦上的改動通常 1 秒內出現在螢幕上。
螢幕上的改動變成小操作送回大腦:
- 單一值(`set`、`bot_set`);
- 清單和對照表逐項修改(`patch`、`bot_patch`:新增或修改某幾項、刪掉某幾項、改順序),所以兩台同時改同一份清單的不同項目,兩邊都會保留;
- 新增、刪除、排序 Bot(`bot_add`、`bot_del`、`bot_order`)。
每個改動有編號,大腦回報收到第幾號;還沒確認的改動,會疊在大腦傳回來的新設定上,打字不會被蓋回去。所有改動和聊天動作由一個工作排隊依序送出。送失敗時,螢幕重新要一份完整資料。
聊天動作(傳訊息、停止、核准、重新產生、開新對話…)送 `act`,由大腦執行。附件先上傳到大腦。
### 金鑰只在大腦
送給螢幕的設定裡,模型金鑰、訊息管道的 Token、連接器的環境變數和標頭都換成「••••(已設定,在大腦上)」。螢幕改設定送回來時,大腦看到這個替代字,就保留原本的值(清單裡依 id 或名字對上)。螢幕要取得模型清單時,由大腦用自己的金鑰去抓。
### 手腳
大腦的 Bot 呼叫工具時可以指定 `computer`。如果是帳號裡的另一台,就透過長連線送 `/api/device/run` 給它,由它在自己身上執行(螢幕、檔案、指令、它的 AnyBot 瀏覽器),再把文字和截圖傳回來。那台要開「裝置連結」才接受;若設了只給某個大腦用(`hands_for`),帳號伺服器會附上是哪一台在問,不是那台就拒絕。指令在那台的工作資料夾(Bot 的 `workdir`)跑;連線經過帳號伺服器只有 35 秒,所以手腳上的指令超過 25 秒就移到背景,用同一台的 `read_terminal` 追蹤(computer 填那台)。
### 配對碼
已登入的手機或 AnyBot 向帳號伺服器要一組 6 位數字(10 分鐘、一次)。新電腦用這組數字換到自己的身分(主機 id 和金鑰),不經過密碼。配對碼記得它是給哪個大腦的,新電腦就只讓那個大腦使用。
### 看圖小幫手
模型拒收圖片時,AnyBot 記下這個模型看不到圖。之後送給它的請求裡,你傳的每張圖會先交給你帳號裡看得到圖的模型(依序試 xAI、Anthropic、OpenAI、Gemini、OpenRouter…,先用你設的預設模型),把描述(包括圖裡的文字)放進訊息。描述依圖片記住,同一張圖不會再看一次;失敗的模型 10 分鐘內跳過。