Permissions and reach
- a file leaving a computer passes the same questions as reading it;
"looking" shell commands that do more than look ask first
- a symlink out of the work/sealed folder counts as outside it
(real paths, not just lexical ones); the search walk skips links
- web_fetch and skill_install read the public web only: localhost, the
LAN and cloud metadata are refused, at every redirect
- preview shows a file without running it; browser_upload may ask
- vault: logins no longer match across shared suffixes (pages.dev,
com.tw, ...); registrable_domain knows public suffixes
- axwrite: found elements are retained (use-after-free)
Relay
- replacing or turning off 2FA needs the current code (rate-limited);
new recovery codes only count once enrollment is verified
- a removed computer loses its socket and its `call` access
- a site name taken by another account answers 409
- RELAY_TRUST_PROXY: the login lock counts the caller's address, not
the proxy's (compose files and README)
- httpd: half a body is not a request
Desktop
- sign-in, join and restore work with 2FA on (code field; --serve reads
ANYBOT_ACCOUNT_CODE; the setup wizard points to a pairing code)
- Windows: text was typed twice; modifiers are held through click, drag
and scroll; horizontal scroll; screen control is no longer refused
- a recording that hits the limit is transcribed, not thrown away
- bot's computer: a new home volume restores the snapshot; a snapshot
is read through before the home is wiped; a failed volume removal
stops reset/restore
- events of a stopped turn no longer land in the next one; each channel
message gets a turn of its own
- saving a bot's settings keeps what it got meanwhile (three-way merge);
a save file that cannot be read is kept
- pages in Big5/GBK/Shift_JIS are read in their own encoding
(encoding_rs); osascript output past 64 KB no longer stalls
- pick_tab keeps a job's own non-http tab; pictures larger than the
texture limit are fitted; the auto-update time field takes typing
- skills live in <data>/skills; memories are unnumbered in the prompt;
the phone may skillify and skill_install
CI
- the test step fails when the tests fail (it piped into tail)
Tests: anybot 268 passed, 27 ignored; relay 16 passed; clippy unchanged.
Not run: Windows on a real machine (win.rs type-checked alone), the
Docker paths of the bot's computer, the live accessibility writes.
Co-authored-by: Cursor <cursoragent@cursor.com>
The person wants the local box and Lima VM off whenever this AnyBot is set
to show another brain, not stopped after two idle minutes.
- sleep_on_a_screen has no idle wait and also runs right at start, before
the loop's first 20-second pause; a resume of an interrupted rebuild is
done on brains only (it would start the box on a screen).
- gvisor-vm.sh no longer registers the VM to start with the Mac: AnyBot
starts it itself when it is the brain and wants its computer.
Verified on this Mac: VM started by hand while AnyBot was closed; the new
AnyBot (a screen of the cloud brain) stopped it 4 seconds after launch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
The Mac mini shows the cloud brain, whose bots work on the brain's own
computer and borrow only this Mac's screen, browser and apps. Its local
box still ran, and the Lima VM under it had grown to its whole 6 GB: the
Mac never gets back memory a VM once used.
- On a screen the box stops after 2 idle minutes, and the Lima VM too when
nothing else runs in it. probe() no longer starts the VM there, and the
volume holder and scheduled updates run on brains only. Becoming a brain
again starts both (probe/ensure_running already wake the VM).
- Screen mode is decided from the settings before the box loop's first
look, which used to start the VM at launch.
- gvisor-vm.sh: 4 GB instead of 6 (box capped at 2 GB), and the VM's
ModemManager, udisks2 and multipathd are turned off.
Verified on this Mac: box and VM stopped by themselves 2 minutes after the
app started; the VM process (4.4 GB plus compressed pages) is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
setup.sh offers 「區網 IP」 (alone, or next to Tailscale): Caddy serves https://<LAN IP> with its
own small certificate authority (tls internal, default_sni for clients that send no name) and
hands out the root at http://<LAN IP>/anybot-ca.crt for each device to trust once.
docker-compose.lan.yml binds Caddy to that address only, so it sits beside tailscale serve.
The app now accepts, for the account server, the certificates this computer trusts as well as
the built-in public ones (HTTP and the WebSocket alike), so it connects once the root is
trusted; before that it says what to install instead of "the certificate is wrong".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha2RVfSYmrhqNnqx9Z4
- box_numbers_only_on_what_is_seen is ignored again (a test inserted before it in cbada6b took
its mark: every cargo test drove the real box), and the path test runs
- gvisor-vm.sh restarts the VM's Docker only when runsc was just registered (a restart stopped
Bot 的電腦), and `${var}` before full-width text (bash read 「)」's first byte as part of
the name and stopped)
- a scheduled box update and a PDF's name use the person's time zone (a server keeps UTC)
- 容器隔離 lists 標準 before 強隔離
- screenshots of real data can open 設定 › 電腦 (ANYBOT_SHOT_VIEW=computers) and scroll
(ANYBOT_SHOT_SCROLL)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha2RVfSYmrhqNnqx9Z4
容器隔離 in 設定 › 電腦: standard (runc, the container is the only wall, Chromium runs
--no-sandbox) or hardened (runsc: a virtualized kernel, all capabilities dropped but the few
the desktop needs, ANYBOT_BOX_SANDBOX=1 turns Chromium's own sandbox back on). Box image v7.
deploy/mac/gvisor-vm.sh makes a Lima VM with Docker + gVisor for a Mac (started at login),
gvisor-migrate.sh moves the box's files and logins there.
Checked and finished before committing:
- the hardened choice is offered only on a Docker host that has runsc; a box set to it on a
host without it opens the standard way and says so (it used to not start at all)
- the settings row shows what the box really runs on, and when 更新 is needed
- no swap only under gVisor (a standard box keeps Docker's default)
- the migrate script sees the running Mac app ("AnyBot", not "anybot")
- the VM script starts an existing VM by name, checks runsc and the kernel from the Mac
- the version test says 7
Real run in the Lima VM: desktop, VNC, numbered controls, xdotool and screenshots work; no
Chromium process has --no-sandbox, renderers run with seccomp and their own namespaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha2RVfSYmrhqNnqx9Z4
agent-desktop comes from GitHub, and only the crates registry and target were cached, so every
server update fetched it again; a slow GitHub made nobledesktop's update fail (cargo exit 101, then
a fetch that hung for over ten minutes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha2RVfSYmrhqNnqx9Z4
Unused (found by taking away the dead-code allowances; nothing else used them, tests aside):
computer::specs, quiet_button, the never-made Poke::Spin, ten icons and their SVGs, the
theme's chrome/bubble_agent colours, ImportRow.line, voice's multipart/transcribe/speak
wrappers, and the old keychain writers vault::save/remove/index_add with their live test
(the vault store replaced them; reading old keychain entries for the import stays).
One copy each: percent-encoding (httpd::url_encode, was four), tool arguments (tools::args_of,
was three), the WebSocket handshake (httpd::accept_websocket, was three), the read pacing
(wire::Pace, was two: relay and app), docker with input and a time limit (dk_with_input, was
two). Docs: FEATURES §看畫面 (VNC), protocol (v* frames, /screen, tickets), architecture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
remote_e2e.py: with E2E_SCREENSHOT=1 a screenshot of the hands comes back to the model as a
JPEG; with E2E_TYPE=1 also a click, typing (Chinese too) and Enter in a terminal there. The fake
model no longer takes a screenshot's picture message for the person speaking. E2E_APP picks the
app to open. deploy/test/wayland: the same test on a headless sway in Docker whose
XDG_RUNTIME_DIR is full (31/31).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A turn started from the phone (not a computer) kept 「don't know」 for its whole run: a message
typed on the Mac while it waited on a hand-off had its 「我的電腦」 moved to the bot's own computer.
Each step now asks which computer the person last typed on, and the brain keeps it beside its
data (person-computer.txt) across restarts. remote_e2e: a second computer, and a mid-turn
message from the other one (fails without the fix); 28 checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A desktop AnyBot left on the server (older build) took "anybot-box" for its own and paused it
every ~20 s as idle while the brain woke it: the bots' computer kept showing as asleep. The
brain now renames its box once (files are in the volume, kept by name) and uses that name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The brain looked the account's computers up once a minute: for up to a minute after the Mac
came on, 「我的電腦」 was unknown and steps went elsewhere. Now every 15 s, and at once when a
message comes from a computer it doesn't know yet.
- A step naming a computer that is off says so in words instead of running on the chosen one
with a note that the person hadn't asked.
- Snapshots: tar's 'file changed' (the open browser) is not a failure; browser caches are left
out; ten minutes for a large home folder.
- box_end_to_end clicks the terminal where it opened; restore only with ANYBOT_TEST_RESTORE=1.
- deploy/test/remote_e2e.py: account server + headless brain + hands + scripted model, 27 checks
(computers, redirect, agent-desktop on the hands, hand-off 3 ways, phone view, sync copy,
edits, hands offline/back, brain restart, clear). anybot --sample-state for such tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every untrusted step popped macOS's prompt and opened Settings; a remote brain's steps covered
the screen. Now once per pane per ten minutes.
- Ad hoc signing made each build a new app to macOS, so 輔助使用/螢幕錄製 silently stopped
working after updates (agent-desktop PERM_DENIED). deploy/mac/sign.sh signs with a local key
kept in its own keychain under AnyBot's folder.
- computer_use_open_app finds 「計算機」 by display name and says the app opened even when its
window can't be read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- setup.sh offers the computer's Tailscale address (HTTPS from tailscale serve, Caddy not
started, nothing open to the internet) — docker-compose.tailscale.yml
- ANYBOT_SETUP_USER / ANYBOT_SETUP_PASSWORD [/ ANYBOT_SETUP_DOMAIN] answer the questions,
for scripts and deployment tools; install.sh server passes them through
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
- install.sh (curl | sh): app, hands, server and deps modes; installs system packages
(right names per release, e.g. libasound2t64 on Ubuntu 24.04), Rust via rustup (1.95+),
Docker for the server; limits parallel compiles on small machines
- Server Docker image: add the ALSA build/runtime packages (the build failed), cache the
cargo registry and target between rebuilds, JOBS build arg
- 加一台電腦 now gives a line that installs AnyBot on a blank computer before joining it
- Makefile: make deps installs Rust too, make doctor, clear message when Rust is missing/old
- README rewritten: one line per use, getting started, update/remove, troubleshooting,
deployment options; docs/install.md and multi-device.md follow it
- Tested on fresh ubuntu:24.04 (app) and debian:12 (curl | sh hands) containers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
- .gitea/workflows/release.yml: on a v* tag, a Linux runner builds and
packages the Ubuntu, headless and server bundles; a Mac runner makes
AnyBot.app. Both attach their files to the tag's release.
- deploy/ci: the Linux build image (Ubuntu 22.04), packaging, release
upload, runner settings, and a server bundle that runs the prebuilt
binaries (no compiling on the server).
- deploy/mac/bundle.sh: AnyBot.app with its icon, signed ad hoc, zipped.
- relay/Dockerfile: copy src/wire.rs too (the relay needs it since the
lasting connection).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
- 加一台電腦 (phone menu and the app's 大腦 card) gives a ten-minute,
one-time code and the line to run: anybot setup <address> <code>. The
new computer joins as hands for that brain with no password and nothing
to answer, starts with the computer and checks the brain sees it.
- anybot setup without arguments is one screen: address and code (a pasted
line fills both), or the account and password instead. No role, name or
confirm questions; a brain is asked only when there are several.
- Hands computers are no longer offered as brains.
- The server setup asks only the domain, account and password.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
- anybot setup: a full-screen terminal guide (role, name, account server,
account, which brain) that saves the settings, installs a systemd user
service (with a virtual screen when there is none), and checks that the
account server and the brain see the computer. Flags for scripts.
- Hands can be limited to one brain; the account server says which AnyBot
asks. Choosing a brain in the app does the same.
- deploy/server/setup.sh: domain, account, sign-ups and keys step by step;
writes .env and Caddyfile, starts everything and checks HTTPS. The
server's brain creates the account on first start.
- deploy/linux/hands.sh installs what's needed and opens anybot setup
(no password kept in a file any more).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
- Each AnyBot keeps one WebSocket to the account server (pinged, reopened
within a second when cut). Requests come down it at once; calls to other
AnyBots of the account (brain sync, hands) go up it. Frames carry raw
bytes, compressed when that saves space. HTTP long-poll stays as fallback.
- The account server knows at once when an AnyBot goes away.
- Edits to lists and maps go item by item, so two computers editing one
list keep both changes.
- Screens fetch chat files from the brain, get model lists through the
brain's keys, and show the brain's computer as the brain's.
- Moving this computer's bots skips starter bots the brain has and bots
already moved.
- The phone goes to the brain instead of a computer that shows it.
- deploy/linux/hands.sh: a Linux computer (with or without a screen) as
hands, kept running by systemd.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa