feat(browser): add optional rust-native backend via fantoccini

* feat(browser): add optional rust-native automation backend

* style: align channels module with stable rustfmt

* fix(browser): switch rust-native backend to fantoccini

Replace headless_chrome with fantoccini to satisfy license checks and keep browser-native optional. Adds native_webdriver_url wiring, migrates native backend session/actions to WebDriver, updates docs/config defaults, and keeps backend auto-resolution behavior intact.

* test(config): serialize env override tests with lock

Prevent flaky CI failures caused by concurrent environment variable mutation across config env-override tests.

* style: apply rustfmt 1.92 for CI parity

* chore(ci): sync lockfile and rustfmt with current main

Resolve feature table drift after rebasing onto latest main, refresh Cargo.lock for browser-native fantoccini, and apply rustfmt 1.92 formatting required by CI.
This commit is contained in:
Chummy 2026-02-16 18:25:27 +08:00 committed by GitHub
parent 9d29f30a31
commit 85fc12bcf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1412 additions and 64 deletions

414
Cargo.lock generated
View file

@ -159,7 +159,7 @@ dependencies = [
"axum-core",
"bytes",
"futures-util",
"http",
"http 1.4.0",
"http-body",
"http-body-util",
"hyper",
@ -191,7 +191,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http 1.4.0",
"http-body",
"http-body-util",
"mime",
@ -390,12 +390,52 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "cookie"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
dependencies = [
"time",
"version_check",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core_maths"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
dependencies = [
"libm",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -433,6 +473,15 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
name = "deranged"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4"
dependencies = [
"powerfmt",
]
[[package]]
name = "dialoguer"
version = "0.11.0"
@ -593,6 +642,29 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fantoccini"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d0086bcd59795408c87a04f94b5a8bd62cba2856cfe656c7e6439061d95b760"
dependencies = [
"base64",
"cookie 0.18.1",
"futures-util",
"http 1.4.0",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"mime",
"serde",
"serde_json",
"time",
"tokio",
"url",
"webdriver",
]
[[package]]
name = "fastrand"
version = "2.3.0"
@ -846,6 +918,17 @@ dependencies = [
"windows-link",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.4.0"
@ -863,7 +946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
"http 1.4.0",
]
[[package]]
@ -874,7 +957,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http 1.4.0",
"http-body",
"pin-project-lite",
]
@ -901,7 +984,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"http",
"http 1.4.0",
"http-body",
"httparse",
"httpdate",
@ -919,10 +1002,12 @@ version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"http 1.4.0",
"hyper",
"hyper-util",
"log",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls",
@ -940,7 +1025,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http 1.4.0",
"http-body",
"hyper",
"ipnet",
@ -977,6 +1062,18 @@ dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke 0.7.5",
"zerofrom",
"zerovec 0.10.4",
]
[[package]]
name = "icu_collections"
version = "2.1.1"
@ -985,9 +1082,9 @@ checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"yoke 0.8.1",
"zerofrom",
"zerovec",
"zerovec 0.11.5",
]
[[package]]
@ -997,10 +1094,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
"litemap 0.8.1",
"tinystr 0.8.2",
"writeable 0.6.2",
"zerovec 0.11.5",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap 0.7.5",
"tinystr 0.7.6",
"writeable 0.5.5",
]
[[package]]
@ -1009,12 +1118,12 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"icu_collections",
"icu_collections 2.1.1",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"icu_provider 2.1.1",
"smallvec",
"zerovec",
"zerovec 0.11.5",
]
[[package]]
@ -1029,12 +1138,12 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
"icu_collections",
"icu_collections 2.1.1",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"icu_provider 2.1.1",
"zerotrie",
"zerovec",
"zerovec 0.11.5",
]
[[package]]
@ -1043,6 +1152,23 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr 0.7.6",
"writeable 0.5.5",
"yoke 0.7.5",
"zerofrom",
"zerovec 0.10.4",
]
[[package]]
name = "icu_provider"
version = "2.1.1"
@ -1051,13 +1177,46 @@ checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"writeable 0.6.2",
"yoke 0.8.1",
"zerofrom",
"zerotrie",
"zerovec",
"zerovec 0.11.5",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "icu_segmenter"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
dependencies = [
"core_maths",
"displaydoc",
"icu_collections 1.5.0",
"icu_locid",
"icu_provider 1.5.0",
"icu_segmenter_data",
"utf8_iter",
"zerovec 0.10.4",
]
[[package]]
name = "icu_segmenter_data"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb"
[[package]]
name = "id-arena"
version = "2.3.0"
@ -1216,6 +1375,12 @@ version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.12"
@ -1243,6 +1408,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "litemap"
version = "0.8.1"
@ -1352,6 +1523,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-traits"
version = "0.2.19"
@ -1388,6 +1565,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "opentelemetry"
version = "0.31.0"
@ -1409,7 +1592,7 @@ checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d"
dependencies = [
"async-trait",
"bytes",
"http",
"http 1.4.0",
"opentelemetry",
"reqwest",
]
@ -1420,7 +1603,7 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf"
dependencies = [
"http",
"http 1.4.0",
"opentelemetry",
"opentelemetry-http",
"opentelemetry-proto",
@ -1548,9 +1731,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
"zerovec 0.11.5",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -1803,7 +1992,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"http",
"http 1.4.0",
"http-body",
"http-body-util",
"hyper",
@ -1898,6 +2087,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
@ -1932,12 +2133,44 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.27"
@ -2227,6 +2460,46 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
]
[[package]]
name = "tinystr"
version = "0.8.2"
@ -2234,7 +2507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
"zerovec 0.11.5",
]
[[package]]
@ -2390,7 +2663,7 @@ dependencies = [
"async-trait",
"base64",
"bytes",
"http",
"http 1.4.0",
"http-body",
"http-body-util",
"percent-encoding",
@ -2437,7 +2710,7 @@ dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http 1.4.0",
"http-body",
"http-body-util",
"iri-string",
@ -2518,7 +2791,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"http 1.4.0",
"httparse",
"log",
"rand 0.8.5",
@ -2787,6 +3060,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webdriver"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91d53921e1bef27512fa358179c9a22428d55778d2c2ae3c5c37a52b82ce6e92"
dependencies = [
"base64",
"bytes",
"cookie 0.16.2",
"http 0.2.12",
"icu_segmenter",
"log",
"serde",
"serde_derive",
"serde_json",
"thiserror 1.0.69",
"time",
"url",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
@ -3192,12 +3485,30 @@ dependencies = [
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive 0.7.5",
"zerofrom",
]
[[package]]
name = "yoke"
version = "0.8.1"
@ -3205,10 +3516,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"yoke-derive 0.8.1",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
@ -3236,6 +3559,7 @@ dependencies = [
"cron",
"dialoguer",
"directories",
"fantoccini",
"futures-util",
"glob",
"hex",
@ -3326,19 +3650,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
"yoke 0.8.1",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke 0.7.5",
"zerofrom",
"zerovec-derive 0.10.3",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"yoke 0.8.1",
"zerofrom",
"zerovec-derive",
"zerovec-derive 0.11.2",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]

View file

@ -39,6 +39,9 @@ prometheus = { version = "0.13", default-features = false }
# Base64 encoding (screenshots, image data)
base64 = "0.22"
# Optional Rust-native browser automation backend
fantoccini = { version = "0.22.0", optional = true, default-features = false, features = ["rustls-tls"] }
# Error handling
anyhow = "1.0"
thiserror = "2.0"
@ -96,6 +99,7 @@ opentelemetry-otlp = { version = "0.31", default-features = false, features = ["
[features]
default = []
browser-native = ["dep:fantoccini"]
# Sandbox backends (platform-specific, opt-in)
sandbox-landlock = ["landlock"] # Linux kernel LSM

View file

@ -128,7 +128,7 @@ Every subsystem is a **trait** — swap implementations with a config change, ze
| **AI Models** | `Provider` | 22+ providers (OpenRouter, Anthropic, OpenAI, Ollama, Venice, Groq, Mistral, xAI, DeepSeek, Together, Fireworks, Perplexity, Cohere, Bedrock, etc.) | `custom:https://your-api.com` — any OpenAI-compatible API |
| **Channels** | `Channel` | CLI, Telegram, Discord, Slack, iMessage, Matrix, WhatsApp, Webhook | Any messaging API |
| **Memory** | `Memory` | SQLite with hybrid search (FTS5 + vector cosine similarity), Markdown | Any persistence backend |
| **Tools** | `Tool` | shell, file_read, file_write, memory_store, memory_recall, memory_forget, browser_open (Brave + allowlist), composio (optional) | Any capability |
| **Tools** | `Tool` | shell, file_read, file_write, memory_store, memory_recall, memory_forget, browser_open (Brave + allowlist), browser (agent-browser / rust-native), composio (optional) | Any capability |
| **Observability** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
| **Runtime** | `RuntimeAdapter` | Native, Docker (sandboxed) | WASM (planned; unsupported kinds fail fast) |
| **Security** | `SecurityPolicy` | Gateway pairing, sandbox, allowlists, rate limits, filesystem scoping, encrypted secrets | — |
@ -302,8 +302,16 @@ provider = "none" # "none", "cloudflare", "tailscale", "ngrok", "c
encrypt = true # API keys encrypted with local key file
[browser]
enabled = false # opt-in browser_open tool
allowed_domains = ["docs.rs"] # required when browser is enabled
enabled = false # opt-in browser_open + browser tools
allowed_domains = ["docs.rs"] # required when browser is enabled
backend = "agent_browser" # "agent_browser" (default), "rust_native", "auto"
native_headless = true # applies when backend uses rust-native
native_webdriver_url = "http://127.0.0.1:9515" # WebDriver endpoint (chromedriver/selenium)
# native_chrome_path = "/usr/bin/chromium" # optional explicit browser binary for driver
# Rust-native backend build flag:
# cargo build --release --features browser-native
# Ensure a WebDriver server is running, e.g. chromedriver --port=9515
[composio]
enabled = false # opt-in: 1000+ OAuth apps via composio.dev

View file

@ -1,5 +1,6 @@
pub mod schema;
#[allow(unused_imports)]
pub use schema::{
AuditConfig, AutonomyConfig, BrowserConfig, ChannelsConfig, ComposioConfig, Config,
DelegateAgentConfig, DiscordConfig, DockerRuntimeConfig, GatewayConfig, HeartbeatConfig,

View file

@ -272,7 +272,7 @@ impl Default for SecretsConfig {
// ── Browser (friendly-service browsing only) ───────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserConfig {
/// Enable `browser_open` tool (opens URLs in Brave without scraping)
#[serde(default)]
@ -283,6 +283,40 @@ pub struct BrowserConfig {
/// Browser session name (for agent-browser automation)
#[serde(default)]
pub session_name: Option<String>,
/// Browser automation backend: "agent_browser" | "rust_native" | "auto"
#[serde(default = "default_browser_backend")]
pub backend: String,
/// Headless mode for rust-native backend
#[serde(default = "default_true")]
pub native_headless: bool,
/// WebDriver endpoint URL for rust-native backend (e.g. http://127.0.0.1:9515)
#[serde(default = "default_browser_webdriver_url")]
pub native_webdriver_url: String,
/// Optional Chrome/Chromium executable path for rust-native backend
#[serde(default)]
pub native_chrome_path: Option<String>,
}
fn default_browser_backend() -> String {
"agent_browser".into()
}
fn default_browser_webdriver_url() -> String {
"http://127.0.0.1:9515".into()
}
impl Default for BrowserConfig {
fn default() -> Self {
Self {
enabled: false,
allowed_domains: Vec::new(),
session_name: None,
backend: default_browser_backend(),
native_headless: default_true(),
native_webdriver_url: default_browser_webdriver_url(),
native_chrome_path: None,
}
}
}
// ── HTTP request tool ───────────────────────────────────────────
@ -1337,6 +1371,7 @@ fn sync_directory(_path: &Path) -> Result<()> {
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
use tempfile::TempDir;
// ── Defaults ─────────────────────────────────────────────
@ -2095,6 +2130,10 @@ default_temperature = 0.7
let b = BrowserConfig::default();
assert!(!b.enabled);
assert!(b.allowed_domains.is_empty());
assert_eq!(b.backend, "agent_browser");
assert!(b.native_headless);
assert_eq!(b.native_webdriver_url, "http://127.0.0.1:9515");
assert!(b.native_chrome_path.is_none());
}
#[test]
@ -2103,12 +2142,23 @@ default_temperature = 0.7
enabled: true,
allowed_domains: vec!["example.com".into(), "docs.example.com".into()],
session_name: None,
backend: "auto".into(),
native_headless: false,
native_webdriver_url: "http://localhost:4444".into(),
native_chrome_path: Some("/usr/bin/chromium".into()),
};
let toml_str = toml::to_string(&b).unwrap();
let parsed: BrowserConfig = toml::from_str(&toml_str).unwrap();
assert!(parsed.enabled);
assert_eq!(parsed.allowed_domains.len(), 2);
assert_eq!(parsed.allowed_domains[0], "example.com");
assert_eq!(parsed.backend, "auto");
assert!(!parsed.native_headless);
assert_eq!(parsed.native_webdriver_url, "http://localhost:4444");
assert_eq!(
parsed.native_chrome_path.as_deref(),
Some("/usr/bin/chromium")
);
}
#[test]
@ -2123,10 +2173,19 @@ default_temperature = 0.7
assert!(parsed.browser.allowed_domains.is_empty());
}
fn env_override_lock() -> std::sync::MutexGuard<'static, ()> {
static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
ENV_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.expect("env override test lock poisoned")
}
// ── Environment variable overrides (Docker support) ─────────
#[test]
fn env_override_api_key() {
let _guard = env_override_lock();
let mut config = Config::default();
assert!(config.api_key.is_none());
@ -2139,6 +2198,7 @@ default_temperature = 0.7
#[test]
fn env_override_api_key_fallback() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::remove_var("ZEROCLAW_API_KEY");
@ -2151,6 +2211,7 @@ default_temperature = 0.7
#[test]
fn env_override_provider() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::set_var("ZEROCLAW_PROVIDER", "anthropic");
@ -2162,6 +2223,7 @@ default_temperature = 0.7
#[test]
fn env_override_provider_fallback() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::remove_var("ZEROCLAW_PROVIDER");
@ -2174,6 +2236,7 @@ default_temperature = 0.7
#[test]
fn env_override_model() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::set_var("ZEROCLAW_MODEL", "gpt-4o");
@ -2185,6 +2248,7 @@ default_temperature = 0.7
#[test]
fn env_override_workspace() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::set_var("ZEROCLAW_WORKSPACE", "/custom/workspace");
@ -2196,6 +2260,7 @@ default_temperature = 0.7
#[test]
fn env_override_empty_values_ignored() {
let _guard = env_override_lock();
let mut config = Config::default();
let original_provider = config.default_provider.clone();
@ -2208,6 +2273,7 @@ default_temperature = 0.7
#[test]
fn env_override_gateway_port() {
let _guard = env_override_lock();
let mut config = Config::default();
assert_eq!(config.gateway.port, 3000);
@ -2220,6 +2286,7 @@ default_temperature = 0.7
#[test]
fn env_override_port_fallback() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::remove_var("ZEROCLAW_GATEWAY_PORT");
@ -2232,6 +2299,7 @@ default_temperature = 0.7
#[test]
fn env_override_gateway_host() {
let _guard = env_override_lock();
let mut config = Config::default();
assert_eq!(config.gateway.host, "127.0.0.1");
@ -2244,6 +2312,7 @@ default_temperature = 0.7
#[test]
fn env_override_host_fallback() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::remove_var("ZEROCLAW_GATEWAY_HOST");
@ -2256,6 +2325,7 @@ default_temperature = 0.7
#[test]
fn env_override_temperature() {
let _guard = env_override_lock();
let mut config = Config::default();
std::env::set_var("ZEROCLAW_TEMPERATURE", "0.5");
@ -2267,6 +2337,7 @@ default_temperature = 0.7
#[test]
fn env_override_temperature_out_of_range_ignored() {
let _guard = env_override_lock();
// Clean up any leftover env vars from other tests
std::env::remove_var("ZEROCLAW_TEMPERATURE");
@ -2286,6 +2357,7 @@ default_temperature = 0.7
#[test]
fn env_override_invalid_port_ignored() {
let _guard = env_override_lock();
let mut config = Config::default();
let original_port = config.gateway.port;
@ -2467,7 +2539,7 @@ temperature = 0.3
max_depth: 3,
},
);
let mut config = Config {
let config = Config {
config_path: config_path.clone(),
workspace_dir: zeroclaw_dir.join("workspace"),
secrets: SecretsConfig { encrypt: true },

File diff suppressed because it is too large Load diff

View file

@ -55,6 +55,7 @@ pub fn default_tools_with_runtime(
}
/// Create full tool registry including memory tools and optional Composio
#[allow(clippy::implicit_hasher)]
pub fn all_tools(
security: &Arc<SecurityPolicy>,
memory: Arc<dyn Memory>,
@ -77,6 +78,7 @@ pub fn all_tools(
}
/// Create full tool registry including memory tools and optional Composio.
#[allow(clippy::implicit_hasher)]
pub fn all_tools_with_runtime(
security: &Arc<SecurityPolicy>,
runtime: Arc<dyn RuntimeAdapter>,
@ -102,11 +104,15 @@ pub fn all_tools_with_runtime(
security.clone(),
browser_config.allowed_domains.clone(),
)));
// Add full browser automation tool (agent-browser)
tools.push(Box::new(BrowserTool::new(
// Add full browser automation tool (pluggable backend)
tools.push(Box::new(BrowserTool::new_with_backend(
security.clone(),
browser_config.allowed_domains.clone(),
browser_config.session_name.clone(),
browser_config.backend.clone(),
browser_config.native_headless,
browser_config.native_webdriver_url.clone(),
browser_config.native_chrome_path.clone(),
)));
}
@ -168,6 +174,7 @@ mod tests {
enabled: false,
allowed_domains: vec!["example.com".into()],
session_name: None,
..BrowserConfig::default()
};
let http = crate::config::HttpRequestConfig::default();
@ -191,6 +198,7 @@ mod tests {
enabled: true,
allowed_domains: vec!["example.com".into()],
session_name: None,
..BrowserConfig::default()
};
let http = crate::config::HttpRequestConfig::default();