Compile the browser-automation core for wasm32: lib/bin split, rt abstraction, injected CDP transport, embedding seams - #1
Conversation
Expose the existing modules through src/lib.rs so the crate can be consumed as a library; src/main.rs keeps the CLI entrypoint and now imports those modules from the lib. Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
…s can supply their own WebSocket Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
The wasm read path silently dropped the timeout and redirect-allowlist enforcement, so gate run_read to native targets and return an unsupported-platform error on wasm32. Track task completion with an AtomicBool so JoinHandle::is_finished reports finished tasks on wasm. Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
|
Tested native parity of this branch end-to-end against real headless Chrome. Result: no behavioral difference from upstream
Branch CLI transcript (all exit codes 0)Environmental note on the earlier DevToolsActivePort failureThe "Chrome exited early without writing DevToolsActivePort" failure in this environment is caused by |
| getrandom = { version = "0.2", features = ["js"] } | ||
| uuid = { version = "1", features = ["v4", "js"] } | ||
| dirs = "5.0" | ||
| reqwest = { version = "0.12", default-features = false, features = ["json", "stream"] } |
There was a problem hiding this comment.
🟨 wasm reqwest client drops rustls-webpki-roots feature used on native
The new wasm dependency block configures reqwest without the rustls-tls-webpki-roots feature that the native block keeps (cli/Cargo.toml:38 vs cli/Cargo.toml:46). On wasm32 reqwest delegates TLS to the host fetch implementation, so this is not directly exploitable, but the asymmetry means any future non-browser wasm host would get a reqwest build with no configured trust anchors rather than the pinned webpki root set the native build relies on.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional: on wasm32 reqwest uses the browser/host fetch backend and delegates TLS entirely to the host, so rustls-tls-webpki-roots has no effect there — and the rustls/ring stack it pulls in does not build for wasm32-unknown-unknown. Enabling it in the wasm block would break the wasm build without adding any trust-anchor behavior. Noted the rationale in the PR description.
…y where wasm compiles it Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
Co-Authored-By: glavin@coframe.com <glavin.wiechert@gmail.com>
End-to-end test results (Devin)Verified native CLI behavior is unchanged on this branch by building the release binary (Rust 1.97.1) and exercising it against real Chrome 143 ( Golden path (all passed): Output seam: Full e2e suite against real Chrome |
Summary
Makes the browser-automation core of agent-browser compile and run as a library on
wasm32-unknown-unknownso non-native hosts (e.g. Cloudflare Workers) can embed the command core (dispatch, snapshot/refs, interaction, network, cookies, storage) and drive a remote browser over an injected CDP WebSocket, without forking the automation semantics. Native CLI/daemon behavior is unchanged, and no host-specific code is added.Building blocks:
lib/bin split — adds
src/lib.rsso the crate exposes a library (agent_browser) alongside the existing binary;main.rsimports from the lib. Native-only top-level modules (chat,doctor,mcp,skills,upgrade) are gated off the wasm build.rtmodule (src/rt.rs) — a small runtime abstraction over task spawning and timers:Native delegates to tokio (
rt::Instantisstd::time::Instant). The wasm implementation drives the same API from the JS event loop (setTimeout,wasm_bindgen_futures::spawn_local, an abortableJoinHandlethat tracks completion). The gating rule is deliberate and narrow: only code that is compiled on wasm switches tocrate::rt; test modules andcfg-gated native-only functions keep calling tokio directly, exactly as onmain.Injected CDP transport (
cdp/client.rs) — the WebSocket is abstracted behind:connect/connect_with_headersbecome thin native wrappers that build the tungstenite transport and callfrom_transport; command multiplexing, the pending-response map, and event broadcast are unchanged and now portable.BrowserManager::from_client(client, ws_url, direct_page)builds a manager around such a client (nativeconnect_cdpnow goes through it too).Library-host seams —
output.rsgainsbegin_capture()/end_capture() -> CapturedOutput: internaloutln!/out!/errln!macros buffer the exact CLI output text when a capture is active and print normally otherwise.src/artifacts.rsroutes file-producing commands (screenshot, PDF, HAR, diff image) throughartifacts::write, which falls back tostd::fs::writeunless a host installs a writer viaset_artifact_writer. Native behavior is identical when no capture/writer is active.Target gating — native-only subsystems (daemon socket server, local Chrome launch/discovery, inspect server, stream/dashboard, WebDriver/Appium transports, ffmpeg recording, plugin subprocesses, install, the
readcommand) are#[cfg(not(target_arch = "wasm32"))]-gated, with explicit "not supported on this platform" errors where a runtime path could be reached.readstays native-only because its client-level timeout and per-hop redirect allowlist cannot be enforced by reqwest's wasm backend.Cargo.tomlsplits target-specific dependencies: native keeps full tokio, tokio-tungstenite, reqwest-rustls, socket2; wasm addswasm-bindgen,js-sys,wasm-bindgen-futureswith tokio limited tomacros+sync. (The wasm reqwest block intentionally omitsrustls-tls-webpki-roots: on wasm32 reqwest delegates TLS to the host fetch implementation and the rustls stack does not build there.)wasm-safe
gen_id—commands::gen_idderives its microsecond timestamp fromjs_sys::Date::now()on wasm32;std::time::SystemTime::now()aborts onwasm32-unknown-unknown(found by running the core in workerd, whereparse_commandtrapped).The
Defaultimpls (DaemonState,RefMap,EventTracker,RecordingState,TracingState) and the#[allow(clippy::should_implement_trait)]onWaitUntil::from_strare needed because clippy lints these pub items now that they are on a lib target (CI runsclippy -- -D warnings).Verification
All from
cli/:cargo check --all-targets: cleancargo check --lib --target wasm32-unknown-unknown: clean (0 errors, 0 warnings)cargo clippy --all-targets -- -D warnings: cleancargo fmt -- --check: cleancargo test: 1065 passed, 0 failedcargo test e2e -- --ignored --test-threads=1) was verified earlier to behave identically tomainin the same environment; native code paths keep using tokio directly wherever they are not compiled for wasm, so native behavior is unchanged by construction (on native targetscrate::rtre-exports the tokio primitives).Link to Devin session: https://app.devin.ai/sessions/5c759851c2274cd7a02ace63935862ef
Requested by: @Glavin001
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.