| Files | Last commit | Last update |
|---|---|---|
fix: restore release CI (#1862) * fix: restore release CI - Accept Windows shutdown semantics and platform-native test paths. - Preserve CDP navigation events across stream session rebinds. - Harden E2E readiness and test environment recovery. * fix: prevent stale frames across stream rebinds - Preserve navigation and runtime events while discarding stale screencast frames during restarts. - Scope frame delivery to the currently bound CDP session. - Cover session and viewport rebind races with regression tests. * fix: invalidate stale stream frames - Clear cached frames when CDP sessions or viewport dimensions change. - Revalidate in-flight frames before publishing after acknowledgements. - Cover rebinds with cached-frame regression tests. | 1 month ago | |
Prefer snapshots over screenshots (#2453) * Prefer snapshots over screenshots in the core skill and chat prompt * Never screenshot to check for an element; wait and re-snapshot * Screenshot only when asked or for content the DOM cannot represent | 1 day ago | |
fix: validate session names before use (#2272) * fix: validate session names before execution Co-Authored-By: Vishnu Jayavel <vishnuj81093@gmail.com> Co-Authored-By: Dwin Gharibi <dwin.gharibi@email.kntu.ac.ir> * docs: document session name rules * style: format session validation regression test * fix: validate sessions only for session commands * fix(mcp): return config errors without exiting Keep the exiting CLI wrapper and use fallible flag parsing for MCP. Cover selected configs removed or corrupted after initialization, and keep config fixtures aligned with explicit trust. --------- Co-authored-by: Vishnu Jayavel <vishnuj81093@gmail.com> Co-authored-by: Dwin Gharibi <dwin.gharibi@email.kntu.ac.ir> | 9 days ago | |
fix(daemon): delete `<session>.config` on daemon exit (#2128) * fix(daemon): delete <session>.config on daemon exit The CLI writes <session>.config once a daemon is ready, but shutdown never removed it, so one file per session name accumulated in the socket directory. Remove it during run_daemon shutdown, before the .pid file, and only while .pid still holds this daemon's pid so a replacement daemon for the same session keeps its config. Fixes #2098 Co-Authored-By: jomatsu <50577611+jomatsu@users.noreply.github.com> * refactor(cli): declare session sidecar files in one place Daemon shutdown and the CLI's stale cleanup each kept their own list of files to remove, and the lists had drifted (.config was missed on shutdown, .engine/.provider/.extensions on stale cleanup). - Add session_files.rs with a SessionFile enum. Each file declares its extension and whether it only describes a running daemon (.target and .log persist). - Both cleanup paths now call remove_daemon_files, which removes every daemon file. - Shutdown removes them only while .pid still holds our pid, so a replacement daemon keeps all of its files, not just .config. - Build every sidecar path through SessionFile instead of format!. Tested with cargo fmt, clippy -D warnings and cargo test on macOS. Not compiled for Windows locally. * fix(cli): route every sidecar deletion through the owned cleanup Review follow-ups on the shared sidecar list. - Drop the early .port (Windows) and .stream deletions in the socket server. They ran before the ownership check, and the final cleanup already removes both. - Dashboard kill used its own list and left .version, .config, .provider and .port behind. It now uses remove_daemon_files, guarded by the killed pid. - Add integration tests that run a real daemon and shut it down: one checks daemon files go and .target/.log stay, one checks a replacement daemon's files are left alone. Both fail if the cleanup block is removed or made unconditional. - Assert literal survivors in the session_files unit test instead of deriving them from is_daemon_file. Tested with cargo fmt, clippy -D warnings and cargo test on macOS. * fix(daemon): stop accepting connections before replying to close (#2138) The daemon replied to close and kept its listener open for another ~100ms before exiting. A command sent right after close returned could connect to the exiting daemon and fail with "Invalid response: EOF", so `close` followed by `open` lost the page. Close now goes through a small handshake: the connection that handled close asks the accept loop to stop, the loop drops the listener, and only then is the reply written. The next command gets a refused connection, cleans up the old session files, and starts a fresh daemon. The old daemon's exit cleanup leaves those new files alone because it only removes files while .pid still holds its own pid. Fixes #2134 Tested with cargo fmt, clippy -D warnings and cargo test on macOS, plus an integration test that fails without the fix. With real Chrome, `open A; close; open B` failed 20/20 before and 0/20 after. --------- Co-authored-by: jomatsu <50577611+jomatsu@users.noreply.github.com> | 13 days ago | |
fix(stream): reuse safe Content-Length parsing (#2268) * fix(stream): share character-safe Content-Length parsing Reuse the existing header parser in both HTTP body readers. Co-Authored-By: Dwin Gharibi <20875684+dwin-gharibi@users.noreply.github.com> * test(stream): cover Content-Length spacing and Unicode values * style(stream): format shared header parsing * test(stream): cover the UTF-8 slicing boundary --------- Co-authored-by: Dwin Gharibi <20875684+dwin-gharibi@users.noreply.github.com> | 9 days ago | |
fix(daemon): decide session reuse and relaunch in one place (#2396) * feat(cli): add session settings option table and patch One table lists every session setting with its flag, effect class (launch or live), persistence class, secrecy and the rule that reads it from the resolved flags. Patch::from_flags builds the set of values an invocation set explicitly, with a wire encoding. Characterization tests check the patch carries every value DaemonOptions and the launch envelope helpers send. Nothing calls the module yet; parse_proxy moves into it unchanged. * refactor(daemon): read session settings through typed views DaemonState holds a Running value seeded once from AGENT_BROWSER_* at startup. The state initializers, auto launch, launch fallbacks, init script and enable lists, CDP headers, the extensions file and the policy and confirm-actions loaders read it through views instead of the environment. Behavior is unchanged: the seed applies each variable's existing parsing rules. The idle timeout resolution moves into the module so one definition serves the daemon and the view. * refactor(daemon): read idle timeout, provider and plugin launch defaults from settings The idle timeout, the provider named in policy checks and the headed, engine, user agent and color scheme defaults handed to plugin providers come from Running instead of the environment. Plugin providers keep their looser headed rule, where any value except 0, false, no or empty counts, behind a dedicated view. * refactor(cli): limit dead code allowances to the patch and option table The running views and the env seed are used by the daemon, so the compiler checks them again. Only the patch, the option table and the settings error keep an allowance until the CLI sends the patch. * test(daemon): pin separator-only allowlist and policy seeding A separator-only allowed domains value seeds an empty list and the resulting filter allows every host. The daemon state test now loads a real action policy file and the plugin provider headed rule has its own tests. * fix(cli): redact secrets from session settings debug output Patch and Running print through a manual Debug that hides secret rows and strips credentials embedded in the proxy URL. * feat(cli): plan a settings patch against the running settings Running::plan compares a patch with the running values and returns the changes, whether the browser has to relaunch, and the settings that follow. A key the patch does not name is left alone, an equal value is free, and setting one connection target displaces the others. Add --relaunch to force a relaunch with no change. * feat(daemon): apply the settings patch carried by each command The daemon plans the patch on every command. Timeouts, policy, confirm actions and auto-dialog apply in place, and launch settings close the browser so the next launch uses them. handle_launch_inner becomes plan plus launch from the session settings, with no per-field env fallbacks. launch_hash is replaced by a launch identity reported in lifecycle only. Every change is reported in lifecycle.settings.changes with secrets redacted, and relaunchedBrowser is set whenever the browser was relaunched. The idle timeout is read on each pass of the shutdown loop so it can change while the daemon runs. * feat(cli): send the settings patch on every command The CLI attaches Patch::from_flags to each command it sends and prints one stderr line per change the daemon reports. The separate launch envelopes, should_send_local_launch_config and attach_obscura_proxy_bypass are gone, and bare open no longer sends a second launch. ensure_daemon restarts the daemon only for --debug. Timeouts, policy, confirm actions and auto-dialog apply in place, so the ignored-timeout warning is removed. * docs: describe settings changes on a running session Cover the per-command patch, the one-line notice for each change, and --relaunch. * fix(cli): keep CDP headers and URL secrets with their endpoint CDP headers are dropped when the CDP endpoint changes unless the same patch sets them. CDP URLs lose userinfo, query and fragment wherever a value is reported. Setting a storage state replaces an inherited profile or extensions, and the reverse, so an inherited setting cannot wedge the launch. A null on the wire no longer clears a security setting. * fix(daemon): commit launch settings only after the launch succeeds A failed launch puts the previous launch settings back, and the failed response still reports what changed. Commands that never launch a browser apply live settings only and leave launch settings pending, so they no longer close the browser or claim a relaunch. * fix(cli): send no launch settings with get cdp-url, print one relaunch line get cdp-url, alone or in a batch, carries live settings only. A settings notice replaces the generic relaunch note. Add relaunch to the MCP common arguments and refresh the tool catalog. * docs: say which settings changes are reported and when * fix(cli): ignore local-only settings for external sessions, send no-xvfb off A session attached over CDP, auto-connect or a provider ignores settings that only describe a browser agent-browser launches itself, so an ambient value cannot relaunch it or start a new cloud session. The ignored requests are reported with effect ignored. AGENT_BROWSER_NO_XVFB set to an off value is now sent. * fix(daemon): apply launch settings at the launch, after the policy gates Live settings apply before the gates. Launch settings are planned there and applied only where the launch happens, so a denied or unconfirmed command leaves the browser running and changes nothing. A failed launch keeps the allowed domains it installed, except when the launch was rejected because they cannot be enforced for it. Settings-driven relaunches invalidate the ref map, an iOS relaunch keeps its device, and the new daemon tests hold the env lock. * fix(cli): send --relaunch once for a batch * docs: describe ignored, pending and kept settings and the env seed * fix(daemon): gate a launch by the provider its settings name A launch that names no connection target connects as the session settings say, so its policy actions now follow them: a settings provider is gated as a browser provider, an external target no longer adds the launch-mutator actions, and the plugin does not run when policy denies or asks to confirm. * fix(daemon): requested CDP headers replace the cached ones Changing or clearing --cdp-headers on a session at the same endpoint updated the settings but reconnected with the headers cached for that endpoint. The cache now follows the plan, and a failed launch puts it back. * fix(daemon): fail the launch when a named --state file cannot be loaded A launch that only inherits the state file still continues without it, as before; one whose command named the file fails, closes the browser and puts the previous launch settings back. * fix(daemon): keep the stored default timeout in step with a live change A command that omits --default-timeout sizes its wait from the daemon's stored config, which kept the value the daemon started with. * docs: list --relaunch after the existing global flags * fix(daemon): a provider named by settings decides the launch policy Policy for a launch now prefers the target the envelope names, then the external target the settings name, then the browser the session is attached to, so switching an attached session to a provider is gated by that provider's plugin and not the old attachment. * fix(daemon): a bare open that carries --state loads it strictly handle_launch_inner looked only at the launch envelope for the state file, so a bare open, which carries it in its settings, launched without state when the file could not be loaded. * fix(daemon): an external session carries no local launch settings Switching to CDP, auto-connect or a provider drops the inherited state file, profile, extensions, proxy and its credentials, executable path and args, and the external launch paths read no state file and install no proxy credentials. A launch that changes where the session connects no longer takes its policy from the old attachment, and an empty provider names no browser, so resetting to a local launch gates the launch mutators. * refactor(daemon): use the launch's storage state binding only once * docs: say inherited local settings are dropped for external sessions * chore: drop a test-generated state file * chore: ignore the parity test state file * fix(daemon): one rule for what names a browser, and a failed switch keeps local settings A blank or zero connection target names no browser in the settings views, the attached target, the launch envelope and the launch policy, so a direct launch with an empty cdpUrl is gated as the local launch it runs. Values dropped because a switch to an external browser failed are put back with the rest of the launch settings. An inherited engine no longer labels a CDP or auto-connect browser. * fix(cli): keep --relaunch for the first batch command that can launch A browserless batch item such as `read` consumed `--relaunch`, so the next command reused the browser. The CLI now asks the daemon's own predicate, `command_launches_browser`, before marking the relaunch as sent. * test(webdriver): change a setting Safari sessions track in the replacement test Headed is local-only, so a Safari session ignores it and the second launch reused the session instead of replacing it. * fix(cli): carry --relaunch until the daemon reports it applied A batch item denied by policy or parked for confirmation never reaches the launch, yet it marked the relaunch as sent, so the next command reused the browser. The batch now reads `lifecycle.settings.forcedRelaunch` from each response instead of predicting it. * fix(cli): count a relaunch reported by an early error or a confirm replay The WebDriver-unsupported and dialog-blocked errors now carry the settings outcome of the launch that ran before them, and the batch reads the replayed command's report nested in a `confirm` response. Without both, the next batch item relaunched a second time. * fix(daemon): confirmation replay runs under the current security settings A command parked under one action policy was replayed with its saved settings, so `confirm` could put back the policy, confirm actions or allowed domains the operator had since tightened. The replay drops security settings, and any change to them drops the pending confirmation. * fix(daemon): re-arm the idle timer from the timeout in effect now The shutdown loop re-armed the timer with the timeout read at the top of the pass, so a live change waited out the previous value, and enabling a timeout that started disabled never armed it. Every re-arm now reads the current timeout and the last activity. * docs: list relaunch as a shared MCP argument and note it applies once per batch * test: cover the CA trust, attached target and CDP headers a failed launch puts back * fix(daemon): keep a parked command's allowed domains in its confirmation replay Allowed domains apply only at the launch, so stripping them from the replay launched a confirmed command without the containment it was parked with. The replay now drops only the action policy and confirm actions; a change to the session's allowed domains already drops the pending confirmation. * docs: every MCP tool accepts relaunch; open and tab_new list it * test(docs): record the relaunch edit to the MCP shared arguments | 4 days ago | |
fix(stream): dispatch input without waiting for the browser's reply (#1627) * fix(stream): dispatch input without waiting for the browser's reply A click arrived one CDP round trip behind every mouse move ahead of it, because the connection's reader awaited Chrome's response before reading the next message. The reply carries nothing this path uses. Measured on a live stream: after a 300-event sweep the click landed 2471ms late, and the delay scaled linearly with the sweep, which is what #1615 describes as "the faster and longer you move the mouse before clicking, the longer the wait". Sending without awaiting takes that to 6ms. Ordering still holds, since every send takes the same socket mutex and the reader calls them in order: a 20-pair click sequence arrives as DUDUDU, a 100-move drag arrives as press, moves, release, and 25 typed characters arrive in sequence. Coalescing mouse moves was considered and left out. The no-await change alone covers the symptom, and dropping events changes what the page observes. Guarded by an ignored e2e that fails at 2493ms with the await restored. E2E does not run on pull requests here, so it first runs on main after merge. Diagnosis from kevingatera in #1615 and #1618. Co-authored-by: kevingatera <kevingatera@users.noreply.github.com> * docs(stream): drop the ack-pacing input-rate caveat, which this fix removes The caveat said a client flooding input on the same socket throttled its own ack-paced stream above roughly 120 messages per second. That was measured, but the cause was misattributed to the client's own send queue: the client's write backlog was zero, which showed the ack had left the client, not that the server had read it. The reader was blocked awaiting CDP, so the ack sat unread. Same root cause as the click latency, so removing the await removes this too. Re-measured on the same page: ack-paced delivery holds 54 to 56 fps at every input rate from 30 to 250 per second, against 3.7 fps at 200/s before. What remains true is the round-trip bound itself, which is a property of the link rather than of client behavior. * test(stream): guard the no-await dispatch on every PR, not only in e2e The existing guard is an ignored e2e, and e2e does not run on pull requests here, so a revert would reach main before anything caught it. This one connects a CdpClient to a CDP endpoint that records commands and never replies. A dispatcher that awaits cannot get past the first event: it fails in 5s on the timeout with "dispatch blocked on a CDP reply that never comes", against 0.03s passing. It also asserts the commands arrive in dispatch order and that pending_len stays 0, so no orphan can accumulate. Suggested by the independent reviewer, which also confirmed no ordering or response-leak defect in the change itself. * docs(stream): state both bounds on ack-paced delivery Said the rate was limited by the link's latency and not its bandwidth. Only the first half is right. One frame is in flight at a time, so a cycle is the frame's transfer plus the acknowledgement round trip, and both terms bound the rate: a thin link is bounded by transfer, a long one by the round trip. Flagged by ctate on #1627. --------- Co-authored-by: kevingatera <kevingatera@users.noreply.github.com> | 3 months ago |
| Files | Last commit | Last update |
|---|---|---|
| 1 month ago | ||
| 1 day ago | ||
| 9 days ago | ||
| 13 days ago | ||
| 9 days ago | ||
| 4 days ago | ||
| 3 months ago |