Add a popup launcher + action-recording panel that builds a copyable
`bsk record start` prompt, add `--browser` targeting, and default `--url`
to https://example.com/ when omitted. Drop the popup browser-alias field.
Move the reaper's silence timeout and scan cadence into DaemonConfig
(defaulting to the existing 60s/15s constants) with a
`with_browser_liveness` builder, so tests can drive the task in
sub-second windows.
Add an integration test that runs a real daemon and asserts a silent
heartbeat-capable browser — and its sessions — get reaped, while a
legacy browser that never heartbeated is left untouched.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only reap browsers that have sent at least one `system.heartbeat`.
A pre-heartbeat extension never opts in, so it is never dropped on
silence and keeps relying on socket-close detection exactly as before.
This removes a backward-compat regression where a new daemon paired
with an old extension would wrongly reap a live-but-idle connection
and then report `no_browser_connected` on the next command.
- Add a `heartbeat_seen` flag to `BrowserClient`, set when a heartbeat
event arrives, and exclude never-heartbeated clients from
`stale_browsers`.
- Cover both the opt-in and the legacy-survivor cases in unit tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
The manifest now declares a minimum Chrome version and requests the
`idle` permission, and the runtime gains the WebSocket heartbeat and
wake-driven reconnect. Publishing that update requires a new version.
Co-authored-by: Cursor <cursoragent@cursor.com>
The extension emits the literal event string "system.heartbeat";
assert the daemon-side serde name serialises to the same value and
round-trips from the exact frame shape the extension sends, so a
future rename cannot silently break keepalive and liveness.
Co-authored-by: Cursor <cursoragent@cursor.com>
After a long idle the service worker is only revived on its next
keepalive-alarm tick (up to 30s away), so the previous 5s wait made
the first command after idle fail with `no_browser_connected` even
though the extension was about to reconnect.
- Raise the daemon's `EXTENSION_CONNECT_WAIT` (used by `session.start`)
to 35s so it comfortably covers one alarm period plus handshake slack.
- Bump the CLI `session start` IPC timeout to that window plus slack so
it never gives up before the daemon answers, and print a one-line
"waiting for browser extension to connect…" hint when it actually
waits.
- Keep `bsk status` / `browsers` on their own short 5s wait so quick
informational commands are not slowed down.
Co-authored-by: Cursor <cursoragent@cursor.com>
Harden the extension's connection so the daemon link survives normal
idle periods and OS sleep instead of silently dropping.
- Send a `system.heartbeat` event every 20s while the post-handshake
link is live. Since Chrome 116 WebSocket activity resets the MV3
service-worker idle timer, so this keeps the worker (and the socket)
alive during use rather than relying on the 30s keepalive alarm that
sits right on the eviction boundary. Declare `minimum_chrome_version:
"116"` accordingly.
- Reconnect on `runtime.onStartup` and `idle.onStateChanged` ("active"),
which revive the worker after a browser restart or machine wake and
let it reconnect immediately instead of waiting for the next alarm
tick. Add the `idle` permission and document it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce a `system.heartbeat` protocol event and use it as a
liveness signal on the daemon side.
- Track `last_seen` per connected browser, bumped on every inbound
WS frame (tool response, event, ping/pong, or heartbeat).
- Add a reaper task that drops any browser silent past a 60s window
and purges its sessions, mirroring the WS disconnect cleanup. This
releases half-open connections left behind when the OS resumes from
sleep and kills the service worker without delivering a close frame,
so the daemon no longer pins a phantom "connected" browser and can
idle-exit normally.
- Wire the reaper into both the foreground daemon and the test/embed
entry point, aborting it on shutdown.
Co-authored-by: Cursor <cursoragent@cursor.com>
Capture user actions in the Agent Window and export a record-only
trace (pages[] + navigate/click/fill/select/press steps with target
descriptors). Adds the record overlay UI aligned with the control
overlay, and wires record start/stop/await through CLI, daemon,
protocol, and extension.
Add `bsk network` as a sibling of the `console` command from #7,
reading the buffered network responses / failures for a tab. Fills the
remaining half of the request in #2 (console landed in #7; network did
not).
Mirrors the console command's conventions exactly:
bsk network --session <id> [--tab-id N] [--since C]
[--limit N] [--max-text-chars N]
The extension enables `Network` on attach (best-effort) and buffers
`responseReceived` / `loadingFailed` events per tab with a monotonic
sequence, correlating them to `requestWillBeSent` for method/URL. Reads
are cursor-paginated (`since` -> `next_since`) and bounded (`limit`,
`max_text_chars`) for agent-context safety, same as console. Entries
carry the real HTTP status code (response) or the CDP failure reason
(failure) — things `performance.getEntriesByType('resource')` can't
give.
Classified read-only (pass-through under the pending-interrupt gate);
defaults to the Agent Window's active tab.
Wires bsk-protocol (Method + params/result/entry structs + schema),
bsk-cli (network subcommand + daemon dispatch allow-list) and the
extension (handler + dispatcher route), with unit / parse / IPC tests
paralleling the console command.
Refs #2.
Send borrow-cancel when the confirmation timeout denies, and add a
timer fallback so auto-deny does not depend only on CSS transitionend.
Co-authored-by: Cursor <cursoragent@cursor.com>