128 lines
10 KiB
Markdown
128 lines
10 KiB
Markdown
# 技術架構
|
||
|
||
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` | 看圖小幫手對每張圖的描述(不用重看) |
|
||
|
||
寫入時先寫暫存檔再改名,不會寫一半。
|
||
|
||
## 安全
|
||
|
||
- **核准關卡**:工具動作照規則直接做、先問或拒絕(`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 分鐘內跳過。
|