Uh oh!
There was an error while loading. Please reload this page.
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:
|
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
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.