diff --git a/FEATURES.md b/FEATURES.md index 4138edd..a59f8b7 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -587,7 +587,7 @@ AnyBot 是一個桌面聊天 App,介面和運作方式照 Grok Bot 重做。 - 每 25 步,模型會在最新的工具結果後面看到一句提醒:方向對就繼續;最近幾步都沒進展就停下來,用一兩句話告訴你卡在哪、要你做什麼(聊天記錄裡看不到這句)。 - 鬼打牆另外有攔截:同一個畫面或網頁動作第 3 次會被警告,第 4 次起不執行。 - 系統提示要模型每一步做多一點:互不相依的工具同一步一起呼叫、查東西的指令合成一條、等待寫在同一條指令裡。 -- **想的跟做的分開**(Bot 設定 › AI 供應商與模型 › 執行步驟用的模型):Bot 的模型負責第一步(想好計畫、開始做)、出問題後的下一步(有步驟失敗、被拒絕、被判定重複、點了之後畫面完全沒變,或快速模型自己從指令改去點畫面、從畫面改去網頁這類換路)、每連續 4 個快速步驟之後看一次進度,以及回合結尾:快速模型在改過東西之後說「做好了」,那句話先擱著不顯示,由 Bot 的模型看過結果再回答。其他照計畫做的步驟交給同一家的快速模型(不長時間思考)。 +- **想的跟做的分開**(Bot 設定 › AI 供應商與模型 › 執行步驟用的模型):Bot 的模型負責第一步(想好計畫、開始做)、出問題後的下一步(有步驟失敗、被拒絕、被判定重複、點了之後畫面完全沒變,或快速模型自己從指令改去點畫面、從畫面改去網頁這類換路)、每連續 4 個快速步驟之後看一次進度,以及回合結尾:快速模型沒動手就要結束回合時(說「做好了」,或停下來問「要我繼續嗎」),那句話先擱著不顯示,由 Bot 的模型看過現況再回答或接著做。一個回合出了兩次問題,剩下的步驟都改用 Bot 的模型。其他照計畫做的步驟交給同一家的快速模型(不長時間思考)。 - 「自動」:xAI 用 grok-4.20-0309-non-reasoning、Anthropic 用 claude-haiku-5-5、Gemini 用 gemini-2.5-flash、OpenAI 用 gpt-5-mini;Bot 的模型本身就是快速型(名字有 non-reasoning、fast、mini、haiku、flash)時不分開。其他供應商不分開。也可以選「不分開」或指定一個模型。 - 快速模型叫不動(帳號沒有這個模型、出錯)時,這一回合剩下的步驟全部改用 Bot 的模型。 - 快速模型的步驟不即時顯示文字(它的答案可能被擱置),工具步驟照常顯示;每一則回覆標的是實際用的模型。 @@ -876,6 +876,7 @@ AnyBot 是一個桌面聊天 App,介面和運作方式照 Grok Bot 重做。 ### 在「Bot 的電腦」上 - 指令只檢查上面那 10 種一律拒絕的,其他都直接做。沙盒會把它關在自己的資料夾裡,看不到也寫不到家目錄的其他地方。 +- box 裡每個 App 都開著無障礙介面(`GTK_MODULES=gail:atk-bridge`):GTK 2 的 App(lxappearance、PCManFM 和很多舊工具)少了 gail 就一個編號都沒有,只能看圖猜座標(box 版本 9 起)。 - Docker 的 Bot 電腦(box)上 sudo 是 Bot 自己的(box 給它免密碼 sudo,可以裝套件、改系統設定):只檢查 sudo 後面接的指令,`sudo rm -rf /`、`sudo mkfs`、`sudo su` 一樣拒絕;含 sudo 的指令用容器裡的 root 執行(gVisor 裡 setuid 不起作用)。以前 sudo 在 box 上也一律拒絕,和「可以 sudo 安裝套件」的說明互相矛盾,Bot 會一直繞路。 - 自己資料夾裡的檔案可以自由讀和改。 - 資料夾外:拒絕,「{Bot} 看不到這台電腦上的其他檔案(路徑)。要處理你的檔案,請用「你的電腦」。」 diff --git a/bench/computer-use/run.py b/bench/computer-use/run.py index 3fe1302..9547d61 100644 --- a/bench/computer-use/run.py +++ b/bench/computer-use/run.py @@ -154,6 +154,21 @@ HELDOUT = [ "for i in 1 2 3 4 5 6 7 8 9 10; do curl -s -o /dev/null http://127.0.0.1:8771/ && break; sleep 0.5; done", "ask": "到 http://127.0.0.1:8771 把深色模式打開、語言改成日本語,按儲存,告訴我頁面上顯示的代碼。", "check": lambda dh, ans: "DJ7Q2" in ans}, + {"id": "N5", "route": "screen", "name": "lxappearance 換佈景", + "setup": "(command -v lxappearance >/dev/null || (apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq lxappearance gnome-themes-extra >/dev/null)); " + "pkill -x lxappearance; rm -f /home/bot/.gtkrc-2.0 /home/bot/.config/gtk-3.0/settings.ini; true", + "ask": "用你電腦上的「外觀設定」程式(lxappearance)把 GTK 佈景主題換成另一個(不是現在用的那個),按「套用」,再告訴我換成了哪一個。", + "check": lambda dh, ans: (lambda t: bool(t.strip()) and t.strip().split("\n")[0].lower() in ans.lower())(dx("sed -n 's/^gtk-theme-name *= *\"*\\([^\"]*\\)\"*$/\\1/p' ~/.config/gtk-3.0/settings.ini ~/.gtkrc-2.0 2>/dev/null | head -1", dh)[1])}, + {"id": "N6", "route": "screen", "name": "PCManFM 建資料夾", + "setup": "(command -v pcmanfm >/dev/null || (apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq pcmanfm >/dev/null)); " + "pkill -x pcmanfm; rm -rf '/home/bot/bench/相片備份'; mkdir -p /home/bot/bench; true", + "ask": "用你電腦上的檔案管理員 PCManFM,在 ~/bench 裡建一個叫「相片備份」的資料夾。要用檔案管理員做,不要用指令。", + # (Whether the file manager did it, not a command, shows in the run's routes.) + "check": lambda dh, ans: dx("test -d ~/bench/相片備份 && echo OK", dh)[1].strip() == "OK"}, + {"id": "N7", "route": "screen", "name": "Mousepad 取代存檔", + "setup": "pkill -x mousepad; mkdir -p /home/bot/bench; printf 'AnyBot 很好用。AnyBot 會操作電腦。\\n' > /home/bot/bench/note.txt; true", + "ask": "用 Mousepad 打開 ~/bench/note.txt,用它的「取代」功能把所有「AnyBot」換成「Bot」,然後存檔。", + "check": lambda dh, ans: dx("cat ~/bench/note.txt", dh)[1].strip() == "Bot 很好用。Bot 會操作電腦。"}, ] ROUTE = [("command", ("shell", "read_file", "write_file", "edit_file", "list_dir", "search", "read_terminal", "write_terminal")), @@ -237,7 +252,7 @@ def main(): # Setup runs as the box's root (sudo can't raise the bot user under gVisor); what it # makes in the bot's home is handed back to the bot. # Every task starts on a clear desktop: apps a previous task left open are closed. - dx("pkill -x galculator; pkill -x xterm; pkill -f '^/usr/lib/chromium/chromium'; sleep 1; true", a.docker_host, user="root") + dx("for a in galculator xterm mousepad lxappearance pcmanfm; do pkill -x $a; done; pkill -f '^/usr/lib/chromium/chromium'; sleep 1; true", a.docker_host, user="root") rc, out = dx(t["setup"].replace("sudo ", "") + "; chown -R bot:bot /home/bot/bench 2>/dev/null; true", a.docker_host, check=False, timeout=900, user="root") print(" setup:", out.strip().replace("\n", " ")[-160:], flush=True) shutil.copyfile(a.template, os.path.join(a.data, "state.json")) diff --git a/box/Dockerfile b/box/Dockerfile index 33a640d..3ba7703 100644 --- a/box/Dockerfile +++ b/box/Dockerfile @@ -35,8 +35,10 @@ RUN chmod +x /usr/local/bin/anybot-box-start /usr/local/bin/anybot-a11y /usr/loc ' export CHROMIUM_FLAGS="$CHROMIUM_FLAGS --no-sandbox --test-type --disable-dev-shm-usage --no-first-run --no-default-browser-check --disable-gpu --password-store=basic"' \ 'fi' > /etc/chromium.d/anybot # Accessibility on for every app, including those started over `docker exec`. -ENV DISPLAY=:1 SCREEN_SIZE=1280x800x24 GTK_MODULES=atk-bridge GTK_A11Y=atspi GNOME_ACCESSIBILITY=1 NO_AT_BRIDGE=0 +# gail too: GTK 2 apps (lxappearance, PCManFM, many older tools) offer no controls to AT-SPI +# without it — no numbers at all, only a picture to guess at. GTK 3 apps ignore it. +ENV DISPLAY=:1 SCREEN_SIZE=1280x800x24 GTK_MODULES=gail:atk-bridge GTK_A11Y=atspi GNOME_ACCESSIBILITY=1 NO_AT_BRIDGE=0 EXPOSE 6080 -LABEL anybot.box.version="8" +LABEL anybot.box.version="9" ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/usr/local/bin/anybot-box-start"] diff --git a/src/agent.rs b/src/agent.rs index 614329e..2ec16d6 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -823,10 +823,11 @@ impl StepPlan { } } - /// The quick model answered without acting: when the turn changed something, the bot's own - /// model checks and answers instead. Returns whether to set that answer aside. + /// The quick model answered without acting: the bot's own model looks at where things stand + /// and answers instead (or carries on). Returns whether to set that answer aside. Not only + /// after changes: a quick model also stopped half way to ask 「要我繼續嗎?」 after one look. pub fn recheck_answer(&mut self, was_quick: bool) -> bool { - let recheck = was_quick && self.changed; + let recheck = was_quick; if recheck { self.main_next = true; } @@ -2060,9 +2061,8 @@ mod tests { assert_eq!(p.pick().as_deref(), Some("q")); } assert_eq!(p.pick(), None, "a look from the bot's own model every QUICK_RUN_MAX steps"); - assert!(!p.recheck_answer(true), "nothing changed: the quick answer stands"); - p.changed = true; - assert!(p.recheck_answer(true) && p.pick().is_none(), "changed: the bot's own model checks and answers"); + assert!(p.recheck_answer(true) && p.pick().is_none(), "the quick model's answer goes to the bot's own model"); + assert!(!p.recheck_answer(false), "the bot's own answer stands"); p.quick_failed(); assert_eq!(p.pick(), None); assert_eq!(StepPlan::new(None).pick(), None); diff --git a/src/app.rs b/src/app.rs index 12c8f06..1f4b660 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4221,6 +4221,7 @@ fn system_prompt(b: &Bot, st: &State, computers: &[tools::Target], default_compu - 換路的規則:一條指令因為環境失敗(Operation not permitted、程式當掉、結果寫著是沙盒擋的),就不要換寫法再試同一條路,改走畫面;畫面上找不到要的東西,先想有沒有指令或專用工具做得到。絕對不要用指令截圖或分析圖片(screencapture、import、PIL):看畫面一律用 computer_use_screenshot,看不清楚就用 computer_use_zoom。\n\ - 像一個熟練的人那樣做事:動手前先想好要做哪幾步、做完怎樣才算成功(照「電腦現況」挑那台電腦上最直接的做法,不用再試探環境);選定一條路就做到底,不要在指令和畫面之間來回換。出錯先讀錯誤訊息想原因再換方法,同一個做法失敗兩次就換。做完只用一種最直接的方法確認一次(讀設定檔、查狀態、看一眼畫面),確認了就回答,不要用好幾種方法反覆確認。\n\ - 沒見過的 App 或畫面也一樣:先看清楚(截圖、需要就放大),找選單、設定、按鈕上的字,照編號點;網頁上的東西用 browser_* 的編號。\n\ + - 使用者交代的事就做完:不要做到一半停下來問「要我繼續嗎」「要我現在做嗎」,畫面不如預期就自己處理(關掉擋路的視窗、打開要用的 App)再接著做。只有需要使用者親手做、或要先確認會切斷連線、刪除、付款這類事情時才停下來問。\n\ - 做不到就照實說哪裡卡住、需要什麼。絕對不要用變通的替代品假裝做到(例如只改環境變數讓時鐘看起來對、自己寫一個同名的視窗或程式),也不要做使用者沒要的額外修改。\n\ - 會切斷你和那台電腦連線的事(關網路、Wi-Fi、VPN、Tailscale、重開機、登出):先告訴使用者做了會斷線、問他要不要做,他說好再做,做之前先把之後要確認的事情想好。\n\ {}\ diff --git a/src/cloudbox.rs b/src/cloudbox.rs index d64015e..daa0412 100644 --- a/src/cloudbox.rs +++ b/src/cloudbox.rs @@ -2850,7 +2850,7 @@ mod tests { use chrono::TimeZone; let at = chrono::Utc.with_ymd_and_hms(2026, 11, 6, 19, 0, 0).unwrap().with_timezone(&chrono_tz::Asia::Taipei); assert!(in_schedule_window("03:00", at.time())); - assert_eq!(wanted_version(), "8"); + assert_eq!(wanted_version(), "9"); } #[test]