GTK 2 apps in the box get numbers, a quick model's answer always goes past the bot's own model, and a bot finishes what it was asked
- Box v9: GTK_MODULES=gail:atk-bridge. lxappearance and PCManFM (GTK 2) offered AT-SPI no controls at all (0 numbers); with gail they offer 37 and 17. - The quick model ending a turn without acting goes to the bot's own model every time, not only after changes: one stopped after a single look to ask 「要我繼續嗎?」. - Instructions: finish what was asked; put an unexpected screen right (close what's in the way, open the app) instead of stopping to ask, except for what the person must do or confirm. - Benchmark: three more held-out tasks (lxappearance theme, PCManFM folder, Mousepad find-and-replace), checked by their results; every task starts with those apps closed too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
This commit is contained in:
parent
3513982f3a
commit
3d10499d55
|
|
@ -587,7 +587,7 @@ AnyBot 是一個桌面聊天 App,介面和運作方式照 Grok Bot 重做。
|
||||||
- 每 25 步,模型會在最新的工具結果後面看到一句提醒:方向對就繼續;最近幾步都沒進展就停下來,用一兩句話告訴你卡在哪、要你做什麼(聊天記錄裡看不到這句)。
|
- 每 25 步,模型會在最新的工具結果後面看到一句提醒:方向對就繼續;最近幾步都沒進展就停下來,用一兩句話告訴你卡在哪、要你做什麼(聊天記錄裡看不到這句)。
|
||||||
- 鬼打牆另外有攔截:同一個畫面或網頁動作第 3 次會被警告,第 4 次起不執行。
|
- 鬼打牆另外有攔截:同一個畫面或網頁動作第 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)時不分開。其他供應商不分開。也可以選「不分開」或指定一個模型。
|
- 「自動」: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 的模型。
|
- 快速模型叫不動(帳號沒有這個模型、出錯)時,這一回合剩下的步驟全部改用 Bot 的模型。
|
||||||
- 快速模型的步驟不即時顯示文字(它的答案可能被擱置),工具步驟照常顯示;每一則回覆標的是實際用的模型。
|
- 快速模型的步驟不即時顯示文字(它的答案可能被擱置),工具步驟照常顯示;每一則回覆標的是實際用的模型。
|
||||||
|
|
@ -876,6 +876,7 @@ AnyBot 是一個桌面聊天 App,介面和運作方式照 Grok Bot 重做。
|
||||||
### 在「Bot 的電腦」上
|
### 在「Bot 的電腦」上
|
||||||
|
|
||||||
- 指令只檢查上面那 10 種一律拒絕的,其他都直接做。沙盒會把它關在自己的資料夾裡,看不到也寫不到家目錄的其他地方。
|
- 指令只檢查上面那 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 會一直繞路。
|
- Docker 的 Bot 電腦(box)上 sudo 是 Bot 自己的(box 給它免密碼 sudo,可以裝套件、改系統設定):只檢查 sudo 後面接的指令,`sudo rm -rf /`、`sudo mkfs`、`sudo su` 一樣拒絕;含 sudo 的指令用容器裡的 root 執行(gVisor 裡 setuid 不起作用)。以前 sudo 在 box 上也一律拒絕,和「可以 sudo 安裝套件」的說明互相矛盾,Bot 會一直繞路。
|
||||||
- 自己資料夾裡的檔案可以自由讀和改。
|
- 自己資料夾裡的檔案可以自由讀和改。
|
||||||
- 資料夾外:拒絕,「{Bot} 看不到這台電腦上的其他檔案(路徑)。要處理你的檔案,請用「你的電腦」。」
|
- 資料夾外:拒絕,「{Bot} 看不到這台電腦上的其他檔案(路徑)。要處理你的檔案,請用「你的電腦」。」
|
||||||
|
|
|
||||||
|
|
@ -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",
|
"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 把深色模式打開、語言改成日本語,按儲存,告訴我頁面上顯示的代碼。",
|
"ask": "到 http://127.0.0.1:8771 把深色模式打開、語言改成日本語,按儲存,告訴我頁面上顯示的代碼。",
|
||||||
"check": lambda dh, ans: "DJ7Q2" in ans},
|
"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")),
|
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
|
# 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.
|
# 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.
|
# 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")
|
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)
|
print(" setup:", out.strip().replace("\n", " ")[-160:], flush=True)
|
||||||
shutil.copyfile(a.template, os.path.join(a.data, "state.json"))
|
shutil.copyfile(a.template, os.path.join(a.data, "state.json"))
|
||||||
|
|
|
||||||
|
|
@ -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"' \
|
' 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
|
'fi' > /etc/chromium.d/anybot
|
||||||
# Accessibility on for every app, including those started over `docker exec`.
|
# 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
|
EXPOSE 6080
|
||||||
LABEL anybot.box.version="8"
|
LABEL anybot.box.version="9"
|
||||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["/usr/local/bin/anybot-box-start"]
|
CMD ["/usr/local/bin/anybot-box-start"]
|
||||||
|
|
|
||||||
12
src/agent.rs
12
src/agent.rs
|
|
@ -823,10 +823,11 @@ impl StepPlan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The quick model answered without acting: when the turn changed something, the bot's own
|
/// The quick model answered without acting: the bot's own model looks at where things stand
|
||||||
/// model checks and answers instead. Returns whether to set that answer aside.
|
/// 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 {
|
pub fn recheck_answer(&mut self, was_quick: bool) -> bool {
|
||||||
let recheck = was_quick && self.changed;
|
let recheck = was_quick;
|
||||||
if recheck {
|
if recheck {
|
||||||
self.main_next = true;
|
self.main_next = true;
|
||||||
}
|
}
|
||||||
|
|
@ -2060,9 +2061,8 @@ mod tests {
|
||||||
assert_eq!(p.pick().as_deref(), Some("q"));
|
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_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");
|
assert!(p.recheck_answer(true) && p.pick().is_none(), "the quick model's answer goes to the bot's own model");
|
||||||
p.changed = true;
|
assert!(!p.recheck_answer(false), "the bot's own answer stands");
|
||||||
assert!(p.recheck_answer(true) && p.pick().is_none(), "changed: the bot's own model checks and answers");
|
|
||||||
p.quick_failed();
|
p.quick_failed();
|
||||||
assert_eq!(p.pick(), None);
|
assert_eq!(p.pick(), None);
|
||||||
assert_eq!(StepPlan::new(None).pick(), None);
|
assert_eq!(StepPlan::new(None).pick(), None);
|
||||||
|
|
|
||||||
|
|
@ -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\
|
- 換路的規則:一條指令因為環境失敗(Operation not permitted、程式當掉、結果寫著是沙盒擋的),就不要換寫法再試同一條路,改走畫面;畫面上找不到要的東西,先想有沒有指令或專用工具做得到。絕對不要用指令截圖或分析圖片(screencapture、import、PIL):看畫面一律用 computer_use_screenshot,看不清楚就用 computer_use_zoom。\n\
|
||||||
- 像一個熟練的人那樣做事:動手前先想好要做哪幾步、做完怎樣才算成功(照「電腦現況」挑那台電腦上最直接的做法,不用再試探環境);選定一條路就做到底,不要在指令和畫面之間來回換。出錯先讀錯誤訊息想原因再換方法,同一個做法失敗兩次就換。做完只用一種最直接的方法確認一次(讀設定檔、查狀態、看一眼畫面),確認了就回答,不要用好幾種方法反覆確認。\n\
|
- 像一個熟練的人那樣做事:動手前先想好要做哪幾步、做完怎樣才算成功(照「電腦現況」挑那台電腦上最直接的做法,不用再試探環境);選定一條路就做到底,不要在指令和畫面之間來回換。出錯先讀錯誤訊息想原因再換方法,同一個做法失敗兩次就換。做完只用一種最直接的方法確認一次(讀設定檔、查狀態、看一眼畫面),確認了就回答,不要用好幾種方法反覆確認。\n\
|
||||||
- 沒見過的 App 或畫面也一樣:先看清楚(截圖、需要就放大),找選單、設定、按鈕上的字,照編號點;網頁上的東西用 browser_* 的編號。\n\
|
- 沒見過的 App 或畫面也一樣:先看清楚(截圖、需要就放大),找選單、設定、按鈕上的字,照編號點;網頁上的東西用 browser_* 的編號。\n\
|
||||||
|
- 使用者交代的事就做完:不要做到一半停下來問「要我繼續嗎」「要我現在做嗎」,畫面不如預期就自己處理(關掉擋路的視窗、打開要用的 App)再接著做。只有需要使用者親手做、或要先確認會切斷連線、刪除、付款這類事情時才停下來問。\n\
|
||||||
- 做不到就照實說哪裡卡住、需要什麼。絕對不要用變通的替代品假裝做到(例如只改環境變數讓時鐘看起來對、自己寫一個同名的視窗或程式),也不要做使用者沒要的額外修改。\n\
|
- 做不到就照實說哪裡卡住、需要什麼。絕對不要用變通的替代品假裝做到(例如只改環境變數讓時鐘看起來對、自己寫一個同名的視窗或程式),也不要做使用者沒要的額外修改。\n\
|
||||||
- 會切斷你和那台電腦連線的事(關網路、Wi-Fi、VPN、Tailscale、重開機、登出):先告訴使用者做了會斷線、問他要不要做,他說好再做,做之前先把之後要確認的事情想好。\n\
|
- 會切斷你和那台電腦連線的事(關網路、Wi-Fi、VPN、Tailscale、重開機、登出):先告訴使用者做了會斷線、問他要不要做,他說好再做,做之前先把之後要確認的事情想好。\n\
|
||||||
{}\
|
{}\
|
||||||
|
|
|
||||||
|
|
@ -2850,7 +2850,7 @@ mod tests {
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
let at = chrono::Utc.with_ymd_and_hms(2026, 11, 6, 19, 0, 0).unwrap().with_timezone(&chrono_tz::Asia::Taipei);
|
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!(in_schedule_window("03:00", at.time()));
|
||||||
assert_eq!(wanted_version(), "8");
|
assert_eq!(wanted_version(), "9");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue