Bots work like someone who knows the computer: they know each machine, reach what they see, plan, and hand the planned steps to a quick model
Measured on the bot's own box (8 tasks x 2, grok-4.7): 14/16 done, 120 steps, 1114 s before;
16/16, 102 steps, 574 s after (model time 1015 -> 471 s). A replayed skill: 49 s -> 15 s.
Knowing the machine
- facts.rs: 電腦現況 per computer (system, desktop, systemd, sudo, network, package manager,
tools, apps, a fixed TZ), read once by a small read-only probe, kept 30 min, in the
instructions at the start of a turn.
- The box's sudo is the bot's: only what follows sudo is checked against the always-denied
list (it was refused outright, while the instructions said it could sudo), and commands with
sudo run as the box's root (setuid does nothing under gVisor).
- Box image v8 drops ENV TZ=UTC (it overrode /etc/localtime). ANYBOT_BOX_IMAGE /
ANYBOT_BOX_VOLUME give a throwaway box its own image and volume, never adopting the real one.
- Mac shell sandbox: App Store app CLIs (they carry their own app sandbox) and ps/top run
outside it; plain `osascript -e` too, asked the first time even in direct mode, refusing do
shell script, terminals, keystrokes, file reads, ObjC and the secret folders. A command the
sandbox broke says so, instead of looking like a broken program.
Reaching what it sees
- The Mac menu bar's status icons are numbered (each app's AXExtrasMenuBar, named from inside
for the system's own), computer_use_zoom looks closer at full sharpness.
- Numbers stay with their controls from look to look (they were reassigned every look, so a
step's second click landed elsewhere); the box's front window is listed first; a control can
be named instead of numbered (name "4").
- Web page numbering walks open shadow DOM (Chromium settings, web components).
- A box on a Docker host elsewhere retries the next port when the usual one is taken (a retry
without it came up with no network).
Working like a person
- Instructions: pick the route, know what done looks like, one route, check once, say what's
stuck instead of faking it with a substitute, ask before cutting the connection.
- StepPlan: the bot's model takes the first step, the step after trouble (failed, refused,
repeated, nothing changed on screen, the quick model switching routes), one in four, and the
final check; a quick model (Bot.step_model, auto: grok-4.20-0309-non-reasoning on xAI) does
the planned steps in between, falling back to the bot's model if it can't be used.
- Skills keep the steps that worked (with what each acted on, not numbers); replay_skill does
them again with no model in between, finding each control again, handing back on a mismatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
2026-11-09 01:01:03 +00:00
// 電腦現況: what a person sitting at each computer would already know before starting — which
// system it is, whether it has a desktop and systemd, whether sudo works, whether the network
// is up, how to install things, which apps are there. Bots without this spent most of a job
// finding it out (or never did: they "set the timezone" through an environment variable because
// they believed sudo was broken, and tried timedatectl on a box without systemd).
//
// Read once per computer by one small read-only script, kept for half an hour, and put in the
// instructions at the start of a turn. The person's own computers are read only when the bot
// may already work on them.
use std ::collections ::HashMap ;
use std ::sync ::Mutex ;
use std ::time ::{ Duration , Instant } ;
use crate ::tools ::{ Place , Target } ;
/// How long a computer's facts are trusted (they change when someone installs or reconfigures).
pub const FRESH_SECS : u64 = 30 * 60 ;
/// Longest a turn waits for facts it doesn't have yet; late answers still fill the cache.
pub const WAIT_MS : u64 = 6_000 ;
/// Apps listed at most (the rest is "…").
const MAX_APPS : usize = 30 ;
/// Read-only, POSIX sh, the same on Linux and macOS: one `key=value` per line.
pub const PROBE : & str = r #"
k ( ) { printf ' % s = % s \ n ' " $1 " " $2 " ; }
k kernel " $(uname -s) " ; k arch " $(uname -m) "
if [ - r / etc / os - release ] ; then . / etc / os - release ; k os " $PRETTY_NAME " ; elif command - v sw_vers > / dev / null 2 > & 1 ; then k os " macOS $(sw_vers -productVersion) " ; fi
[ " $(uname -s) " = Linux ] & & { [ - d / run / systemd / system ] & & k systemd yes | | k systemd no ; }
if [ " $(id -u) " = 0 ] ; then k sudo root ; elif command - v sudo > / dev / null 2 > & 1 ; then if sudo - n true > / dev / null 2 > & 1 ; then k sudo yes ; else k sudo password ; fi ; else k sudo none ; fi
if command - v curl > / dev / null 2 > & 1 ; then curl - s - m 4 - o / dev / null https ://www.google.com/generate_204 && k net yes || k net no; fi
for p in apt - get dnf yum pacman apk zypper brew ; do command - v $p > / dev / null 2 > & 1 & & k pkg $p ; done
k desktop " ${XDG_CURRENT_DESKTOP:-} "
[ - n " ${TZ:-} " ] & & k tz " $TZ "
for w in fluxbox gnome - shell plasmashell xfce4 - session mate - session cinnamon sway Hyprland ; do pgrep - x $w > / dev / null 2 > & 1 & & k wm $w ; done
t = " " ; for x in python3 node npm git docker curl wget jq ffmpeg xdotool wmctrl osascript shortcuts brew ; do command - v $x > / dev / null 2 > & 1 & & t = " $t $x " ; done ; k tools " $t "
if [ - d / usr / share / applications ] ; then k apps " $(for f in /usr/share/applications/*.desktop; do sed -n 's/^Name=//p' " $f " | head -1; done 2>/dev/null | sort -u | head -60 | tr ' \n ' '|') " ; fi
if [ - d / Applications ] ; then k apps " $(ls /Applications 2>/dev/null | sed -n 's/ \ .app$//p' | head -80 | tr ' \n ' '|') " ; fi
" #;
#[ derive(Default, Debug, Clone, PartialEq) ]
pub struct Facts {
pub kernel : String ,
pub arch : String ,
pub os : String ,
/// Some(true/false) on Linux; None elsewhere.
pub systemd : Option < bool > ,
/// root, yes, password, none.
pub sudo : String ,
pub net : Option < bool > ,
pub pkg : Vec < String > ,
pub desktop : String ,
/// A TZ variable every program is started with (it overrides /etc/localtime).
pub tz : String ,
pub wm : Vec < String > ,
pub tools : Vec < String > ,
pub apps : Vec < String > ,
}
pub fn parse ( out : & str ) -> Facts {
let mut f = Facts ::default ( ) ;
for line in out . lines ( ) {
let Some ( ( k , v ) ) = line . split_once ( '=' ) else { continue } ;
let v = v . trim ( ) ;
match k . trim ( ) {
" kernel " = > f . kernel = v . into ( ) ,
" arch " = > f . arch = v . into ( ) ,
" os " = > f . os = v . into ( ) ,
" systemd " = > f . systemd = Some ( v = = " yes " ) ,
" sudo " = > f . sudo = v . into ( ) ,
" net " = > f . net = Some ( v = = " yes " ) ,
" pkg " = > f . pkg . push ( v . into ( ) ) ,
" desktop " = > f . desktop = v . into ( ) ,
" tz " = > f . tz = v . into ( ) ,
" wm " = > f . wm . push ( v . into ( ) ) ,
" tools " = > f . tools = v . split_whitespace ( ) . map ( String ::from ) . collect ( ) ,
" apps " = > f . apps . extend ( v . split ( '|' ) . map ( str ::trim ) . filter ( | a | ! a . is_empty ( ) ) . map ( String ::from ) ) ,
_ = > { }
}
}
f . apps . sort ( ) ;
f . apps . dedup ( ) ;
f
}
/// One line about a computer, in the words the bot is instructed in. `own_box`: the bot's own
/// box, where AnyBot runs sudo commands as root (sudo itself can't rise under gVisor).
pub fn describe ( name : & str , f : & Facts , own_box : bool , mac_sandbox : bool ) -> String {
let mut parts : Vec < String > = vec! [ ] ;
let os = if f . os . is_empty ( ) { f . kernel . clone ( ) } else { f . os . clone ( ) } ;
parts . push ( if f . arch . is_empty ( ) { os } else { format! ( " {os} ( {} ) " , f . arch ) } ) ;
let desk = if ! f . wm . is_empty ( ) { f . wm . join ( " 、 " ) } else { f . desktop . clone ( ) } ;
if ! desk . is_empty ( ) {
parts . push ( format! ( " 桌面 {desk} " ) ) ;
}
match f . systemd {
Some ( false ) = > parts . push ( " 沒有 systemd( systemctl、timedatectl、hostnamectl 都不能用:改系統設定就直接改設定檔,例如時區是 /etc/localtime 和 /etc/timezone) " . into ( ) ) ,
Some ( true ) = > parts . push ( " 有 systemd " . into ( ) ) ,
None = > { }
}
let sudo = if own_box {
" 可以 sudo( 免密碼; AnyBot 會用 root 執行含 sudo 的指令) "
} else {
match f . sudo . as_str ( ) {
" root " = > " 指令以 root 執行 " ,
" yes " = > " 可以 sudo( 免密碼) " ,
" password " if mac_sandbox = > " sudo 在指令沙盒裡不能用 " ,
" password " = > " sudo 要密碼(不要嘗試,需要時請使用者自己執行) " ,
" none " = > " 沒有 sudo " ,
_ = > " " ,
}
} ;
if ! sudo . is_empty ( ) {
parts . push ( sudo . into ( ) ) ;
}
if ! f . tz . is_empty ( ) & & f . kernel ! = " Darwin " {
parts . push ( format! ( " 每個程式都帶著環境變數 TZ= {} (容器設定的,會蓋過 /etc/localtime: 改了系統時區, date 和桌面時鐘還是顯示 {} ;確認系統時區用 env -u TZ date, 或 readlink /etc/localtime) " , f . tz , f . tz ) ) ;
}
match f . net {
Some ( true ) = > parts . push ( " 有網路 " . into ( ) ) ,
Some ( false ) = > parts . push ( " 連不到網路(不要嘗試下載或安裝,先告訴使用者) " . into ( ) ) ,
None = > { }
}
if let Some ( p ) = f . pkg . iter ( ) . find ( | p | p . as_str ( ) ! = " brew " ) . or ( f . pkg . first ( ) ) {
parts . push ( format! ( " 裝東西用 {p} " ) ) ;
}
if ! f . tools . is_empty ( ) {
parts . push ( format! ( " 指令有 {} " , f . tools . join ( " 、 " ) ) ) ;
}
if mac_sandbox {
parts . push ( " 指令在 AnyBot 的沙盒裡跑: ps、top、App Store 版 App 自己的指令列、單獨一條 osascript -e 會在沙盒外跑,其他碰不到祕密資料夾 " . into ( ) ) ;
}
2026-11-09 06:58:40 +00:00
// What anyone used to the system knows about its standard windows and dialogs: an app the bot
// never saw still uses them (it lost minutes to TextEdit's save panel, then changed the format
// nobody asked about).
if f . kernel = = " Darwin " {
parts . push ( " Mac 的操作慣例:存檔、開檔對話框和 Finder 裡按 cmd+shift+G 可以直接打資料夾的完整路徑再按 Return; 存檔對話框在「儲存為」打檔名後按 Return 就存好了; cmd+W 關掉目前的視窗、cmd+Q 結束 App( 有沒存的會先問) ; 檔案格式、副檔名這類選項使用者沒說就照預設, 不要另外改; 指令是用 /bin/sh 跑的, echo -n 會把「-n」也寫出去, 要不換行就用 printf; 讀到的結果和預期不同( 多了字、少了字) 就照實說, 不要說成預期的樣子 " . into ( ) ) ;
} else if f . kernel = = " Linux " & & ( ! f . wm . is_empty ( ) | | ! f . desktop . is_empty ( ) ) {
parts . push ( " Linux 桌面的操作慣例: GTK 的開檔、存檔對話框按 ctrl+L 可以直接打完整路徑再按 Return; ctrl+S 存檔、ctrl+W 關視窗、ctrl+Q 結束 App; 檔案格式這類選項使用者沒說就照預設 " . into ( ) ) ;
}
Bots work like someone who knows the computer: they know each machine, reach what they see, plan, and hand the planned steps to a quick model
Measured on the bot's own box (8 tasks x 2, grok-4.7): 14/16 done, 120 steps, 1114 s before;
16/16, 102 steps, 574 s after (model time 1015 -> 471 s). A replayed skill: 49 s -> 15 s.
Knowing the machine
- facts.rs: 電腦現況 per computer (system, desktop, systemd, sudo, network, package manager,
tools, apps, a fixed TZ), read once by a small read-only probe, kept 30 min, in the
instructions at the start of a turn.
- The box's sudo is the bot's: only what follows sudo is checked against the always-denied
list (it was refused outright, while the instructions said it could sudo), and commands with
sudo run as the box's root (setuid does nothing under gVisor).
- Box image v8 drops ENV TZ=UTC (it overrode /etc/localtime). ANYBOT_BOX_IMAGE /
ANYBOT_BOX_VOLUME give a throwaway box its own image and volume, never adopting the real one.
- Mac shell sandbox: App Store app CLIs (they carry their own app sandbox) and ps/top run
outside it; plain `osascript -e` too, asked the first time even in direct mode, refusing do
shell script, terminals, keystrokes, file reads, ObjC and the secret folders. A command the
sandbox broke says so, instead of looking like a broken program.
Reaching what it sees
- The Mac menu bar's status icons are numbered (each app's AXExtrasMenuBar, named from inside
for the system's own), computer_use_zoom looks closer at full sharpness.
- Numbers stay with their controls from look to look (they were reassigned every look, so a
step's second click landed elsewhere); the box's front window is listed first; a control can
be named instead of numbered (name "4").
- Web page numbering walks open shadow DOM (Chromium settings, web components).
- A box on a Docker host elsewhere retries the next port when the usual one is taken (a retry
without it came up with no network).
Working like a person
- Instructions: pick the route, know what done looks like, one route, check once, say what's
stuck instead of faking it with a substitute, ask before cutting the connection.
- StepPlan: the bot's model takes the first step, the step after trouble (failed, refused,
repeated, nothing changed on screen, the quick model switching routes), one in four, and the
final check; a quick model (Bot.step_model, auto: grok-4.20-0309-non-reasoning on xAI) does
the planned steps in between, falling back to the bot's model if it can't be used.
- Skills keep the steps that worked (with what each acted on, not numbers); replay_skill does
them again with no model in between, finding each control again, handing back on a mismatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
2026-11-09 01:01:03 +00:00
if ! f . apps . is_empty ( ) {
let more = if f . apps . len ( ) > MAX_APPS { " … " } else { " " } ;
parts . push ( format! ( " 裝了的 App: {} {more} " , f . apps . iter ( ) . take ( MAX_APPS ) . cloned ( ) . collect ::< Vec < _ > > ( ) . join ( " 、 " ) ) ) ;
}
format! ( " 「 {name} 」: {} 。 " , parts . join ( " ; " ) )
}
fn cache ( ) -> & 'static Mutex < HashMap < String , ( Instant , String ) > > {
static C : std ::sync ::OnceLock < Mutex < HashMap < String , ( Instant , String ) > > > = std ::sync ::OnceLock ::new ( ) ;
C . get_or_init ( | | Mutex ::new ( HashMap ::new ( ) ) )
}
/// Which computers to read, and whether they may be read: the bot's own always; the person's
/// only when the bot may already work there (no command runs on them for this otherwise).
fn readable ( t : & Target , may_use_persons : bool ) -> bool {
match & t . place {
Place ::Box { .. } = > crate ::cloudbox ::run_state ( ) . is_running ( ) ,
Place ::Local { .. } = > true ,
Place ::Device { .. } | Place ::Ssh { .. } = > may_use_persons ,
Place ::Sealed { .. } = > false ,
}
}
/// Run the probe on one computer. The person's own Mac (this one) is read by AnyBot itself, not
/// as one of the bot's commands: nothing the bot asked runs there.
fn probe ( t : & Target , via : & str ) -> Result < String , String > {
match & t . place {
Place ::Box { .. } = > crate ::cloudbox ::read_facts ( PROBE ) ,
Place ::Local { .. } = > {
let out = std ::process ::Command ::new ( " /bin/sh " ) . arg ( " -c " ) . arg ( PROBE ) . env ( " PATH " , crate ::cloudbox ::tool_path ( ) ) . output ( ) . map_err ( | e | e . to_string ( ) ) ? ;
Ok ( String ::from_utf8_lossy ( & out . stdout ) . to_string ( ) )
}
Place ::Device { host , .. } = > {
let args = serde_json ::json! ( { " command " : PROBE , " timeout_ms " : 8000 } ) . to_string ( ) ;
crate ::remote ::run_on_device ( host , " shell " , & args , via ) . map ( | v | v [ " text " ] . as_str ( ) . unwrap_or ( " " ) . to_string ( ) )
}
Place ::Ssh { .. } = > {
let cancel = std ::sync ::atomic ::AtomicBool ::new ( false ) ;
let ids = crate ::tools ::RunIds { conversation : " facts " . into ( ) , request : " facts " . into ( ) } ;
let args = serde_json ::json! ( { " command " : PROBE , " timeout_ms " : 8000 } ) . to_string ( ) ;
let ( out , ok ) = crate ::tools ::run_on ( " shell " , & args , t , & cancel , & ids ) ;
if ok { Ok ( out ) } else { Err ( out ) }
}
Place ::Sealed { .. } = > Err ( " no " . into ( ) ) ,
}
}
fn key ( t : & Target ) -> String {
format! ( " {} | {:?} " , t . name , t . place )
}
/// The facts section for a turn's instructions: cached facts, plus fresh reads of the rest (at
/// most WAIT_MS for all of them together; a slow one is used next turn).
pub fn for_turn ( computers : & [ Target ] , may_use_persons : bool , via : & str ) -> String {
let wanted : Vec < & Target > = computers . iter ( ) . filter ( | t | readable ( t , may_use_persons ) ) . collect ( ) ;
let fresh = | t : & Target | cache ( ) . lock ( ) . unwrap_or_else ( | e | e . into_inner ( ) ) . get ( & key ( t ) ) . filter ( | ( at , _ ) | at . elapsed ( ) . as_secs ( ) < FRESH_SECS ) . map ( | ( _ , l ) | l . clone ( ) ) ;
let missing : Vec < Target > = wanted . iter ( ) . filter ( | t | fresh ( t ) . is_none ( ) ) . map ( | t | ( * t ) . clone ( ) ) . collect ( ) ;
if ! missing . is_empty ( ) {
let ( tx , rx ) = std ::sync ::mpsc ::channel ( ) ;
for t in missing {
let tx = tx . clone ( ) ;
let via = via . to_string ( ) ;
std ::thread ::spawn ( move | | {
if let Ok ( out ) = probe ( & t , & via ) {
let f = parse ( & out ) ;
if ! f . kernel . is_empty ( ) | | ! f . os . is_empty ( ) {
let mac_sandbox = matches! ( t . place , Place ::Local { .. } ) & & cfg! ( target_os = " macos " ) | | f . kernel = = " Darwin " & & matches! ( t . place , Place ::Device { .. } ) ;
let line = describe ( & t . name , & f , matches! ( t . place , Place ::Box { .. } ) , mac_sandbox ) ;
cache ( ) . lock ( ) . unwrap_or_else ( | e | e . into_inner ( ) ) . insert ( key ( & t ) , ( Instant ::now ( ) , line ) ) ;
}
}
let _ = tx . send ( ( ) ) ;
} ) ;
}
drop ( tx ) ;
let end = Instant ::now ( ) + Duration ::from_millis ( WAIT_MS ) ;
while let Some ( left ) = end . checked_duration_since ( Instant ::now ( ) ) {
if rx . recv_timeout ( left ) . is_err ( ) {
break ;
}
}
}
let lines : Vec < String > = wanted . iter ( ) . filter_map ( | t | fresh ( t ) ) . collect ( ) ;
if lines . is_empty ( ) {
return String ::new ( ) ;
}
format! (
" \n \n 電腦現況( AnyBot 剛剛自動查的,照這個選做法,不用再花步驟去試探): \n {} " ,
lines . iter ( ) . map ( | l | format! ( " - {l} " ) ) . collect ::< Vec < _ > > ( ) . join ( " \n " )
)
}
#[ cfg(test) ]
mod tests {
use super ::* ;
const BOX : & str = " kernel=Linux \n arch=aarch64 \n os=Debian GNU/Linux 13 (trixie) \n systemd=no \n sudo=password \n net=yes \n pkg=apt-get \n desktop= \n tz=UTC \n wm=fluxbox \n tools= python3 node npm git curl wget jq xdotool \n apps=Chromium|XTerm|galculator| \n " ;
const MAC : & str = " kernel=Darwin \n arch=arm64 \n os=macOS 26.1 \n sudo=password \n net=yes \n pkg=brew \n desktop= \n tools= python3 git curl osascript shortcuts brew \n apps=Tailscale|LINE|Safari| \n " ;
#[ test ]
fn a_box_reads_like_a_person_would_know_it ( ) {
let f = parse ( BOX ) ;
assert_eq! ( f . systemd , Some ( false ) ) ;
assert_eq! ( f . net , Some ( true ) ) ;
assert_eq! ( f . apps , vec! [ " Chromium " , " XTerm " , " galculator " ] ) ;
let line = describe ( " Bot 的電腦 " , & f , true , false ) ;
for want in [ " Debian GNU/Linux 13 " , " fluxbox " , " 沒有 systemd " , " /etc/localtime " , " 可以 sudo( 免密碼 " , " 有網路 " , " apt-get " , " galculator " , " TZ=UTC " , " env -u TZ date " ] {
assert! ( line . contains ( want ) , " {want}: {line} " ) ;
}
}
#[ test ]
fn a_mac_says_what_its_sandbox_lets_through ( ) {
let line = describe ( " 王小明的Mac mini " , & parse ( MAC ) , false , true ) ;
assert! ( line . contains ( " macOS 26.1 " ) & & line . contains ( " 沙盒 " ) & & line . contains ( " osascript " ) & & line . contains ( " 裝東西用 brew " ) & & line . contains ( " Tailscale " ) , " {line} " ) ;
2026-11-09 06:58:40 +00:00
assert! ( line . contains ( " cmd+shift+G " ) & & line . contains ( " 照預設 " ) & & line . contains ( " printf " ) , " {line} " ) ;
assert! ( describe ( " Bot 的電腦 " , & parse ( BOX ) , true , false ) . contains ( " ctrl+L " ) ) ;
Bots work like someone who knows the computer: they know each machine, reach what they see, plan, and hand the planned steps to a quick model
Measured on the bot's own box (8 tasks x 2, grok-4.7): 14/16 done, 120 steps, 1114 s before;
16/16, 102 steps, 574 s after (model time 1015 -> 471 s). A replayed skill: 49 s -> 15 s.
Knowing the machine
- facts.rs: 電腦現況 per computer (system, desktop, systemd, sudo, network, package manager,
tools, apps, a fixed TZ), read once by a small read-only probe, kept 30 min, in the
instructions at the start of a turn.
- The box's sudo is the bot's: only what follows sudo is checked against the always-denied
list (it was refused outright, while the instructions said it could sudo), and commands with
sudo run as the box's root (setuid does nothing under gVisor).
- Box image v8 drops ENV TZ=UTC (it overrode /etc/localtime). ANYBOT_BOX_IMAGE /
ANYBOT_BOX_VOLUME give a throwaway box its own image and volume, never adopting the real one.
- Mac shell sandbox: App Store app CLIs (they carry their own app sandbox) and ps/top run
outside it; plain `osascript -e` too, asked the first time even in direct mode, refusing do
shell script, terminals, keystrokes, file reads, ObjC and the secret folders. A command the
sandbox broke says so, instead of looking like a broken program.
Reaching what it sees
- The Mac menu bar's status icons are numbered (each app's AXExtrasMenuBar, named from inside
for the system's own), computer_use_zoom looks closer at full sharpness.
- Numbers stay with their controls from look to look (they were reassigned every look, so a
step's second click landed elsewhere); the box's front window is listed first; a control can
be named instead of numbered (name "4").
- Web page numbering walks open shadow DOM (Chromium settings, web components).
- A box on a Docker host elsewhere retries the next port when the usual one is taken (a retry
without it came up with no network).
Working like a person
- Instructions: pick the route, know what done looks like, one route, check once, say what's
stuck instead of faking it with a substitute, ask before cutting the connection.
- StepPlan: the bot's model takes the first step, the step after trouble (failed, refused,
repeated, nothing changed on screen, the quick model switching routes), one in four, and the
final check; a quick model (Bot.step_model, auto: grok-4.20-0309-non-reasoning on xAI) does
the planned steps in between, falling back to the bot's model if it can't be used.
- Skills keep the steps that worked (with what each acted on, not numbers); replay_skill does
them again with no model in between, finding each control again, handing back on a mismatch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha5F5gJt93t3yEx3XcS
2026-11-09 01:01:03 +00:00
assert! ( ! line . contains ( " systemd " ) , " {line} " ) ;
// Offline and no sudo, said plainly so the bot doesn't try.
let off = describe ( " x " , & parse ( " kernel=Linux \n net=no \n sudo=password \n " ) , false , false ) ;
assert! ( off . contains ( " 連不到網路 " ) & & off . contains ( " sudo 要密碼 " ) , " {off} " ) ;
}
/// The probe runs here and says something sensible (any Unix).
#[ test ]
fn the_probe_runs_on_this_computer ( ) {
let out = std ::process ::Command ::new ( " /bin/sh " ) . arg ( " -c " ) . arg ( PROBE ) . output ( ) . unwrap ( ) ;
let f = parse ( & String ::from_utf8_lossy ( & out . stdout ) ) ;
assert! ( ! f . kernel . is_empty ( ) & & ! f . arch . is_empty ( ) , " {f:?} " ) ;
}
}