AnyBot/docs/architecture.md

12 KiB
Raw Permalink Blame History

技術架構

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 一個回合:組系統指示、送模型、收串流、跑工具、核准關卡、長對話摘要;每一步用哪個模型(StepPlan:Bot 的模型規劃、出問題時和收尾,快速模型照計畫執行)
facts.rs 電腦現況:每台電腦一段唯讀小腳本讀出系統、桌面、systemd、sudo、網路、套件管理、常用指令和 App,30 分鐘快取,回合開始時放進系統指示
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 連得動)。只有沙盒管得到的才是祕密防線,核准層不再逐字掃路徑。例外(tools::runs_outside_sandbox,只在你的電腦上):App Store 版 App 的指令列(自帶 App 沙盒,在這裡會兩層衝突而當掉)、ps/top(setuid,沙盒裡不能跑)、單獨一條且不含危險寫法的 osascript -e(沙盒裡指揮別的 App 一律 -10004;第一次先問,直接操作模式也問,osascript:outside 授權後不再問)。AppleScript 在沙盒外沒有東西擋它讀檔,所以靠寫法過濾加第一次詢問,不是密不透風。環境變數裡的模型金鑰會先拿掉,PATH 會補齊 Homebrew 等位置(從 Finder 或 launchd 啟動也要找得到指令)。
  • Bot 的電腦:放在容器裡,和你的電腦分開。標準(runc)容器和主機共用 Linux 核心,Chromium 在裡面關掉自己的沙盒,容器就是唯一那道牆。Docker 主機裝了 gVisor 時,設定 › 電腦 › 容器隔離可改「強隔離(gVisor)」:容器有自己的虛擬核心,Chromium 的沙盒重新開啟,牆變成兩道(虛擬核心+瀏覽器沙盒)。改完要按電腦的「更新」重建才生效。gVisor 裡 setuid 不起作用,bot 使用者用 sudo 會失敗(「effective uid is not 0」),所以含 sudo 的指令改用容器裡的 root 執行(cloudbox::exec_wrapper),跑完把它在 bot 家目錄留下的檔案還給 bot。在 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。

大腦和螢幕的同步(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 分鐘內跳過。