Repository navigation
feat: Mini Browser — a live Chromium that agents drive and users can take over - #483
korivi-CraftOS wants to merge 8 commits into
Conversation
Squash of the original korivi-Exp1 prototype (web agent V1/V2, password vault, onboarding error wrap) applied on top of current main. Conflicts resolved against main's newer browser adapter and sidebar; the local-only slow_mode and profile-interview workarounds were dropped. The page still targets the old task API; it is adapted in the next commits.
A real Chromium that agents drive and the user watches live, usable by every kind of agent (main and chat sessions, a dedicated Mini Browser chat, scheduled runs, Agent App trigger runs and the new browser_agent sub-agent). Engine (app/mini_browser): - dedicated "mini-browser" thread + event loop owns Playwright; callers on any loop marshal in (fixes sub-agents, which run on their own loops) - one tab per agent session with per-tab locks, owner labels, take-control, follow-view, crash and driver-death recovery, idle shutdown, profile lock - live view via CDP screencast of the viewed tab, only while a UI tab is subscribed, latest-frame-wins delivery - ad blocking via CDP Network.setBlockedURLs (context.route disabled the HTTP cache); CraftBot's own UI origins blocked browser-wide - human-like mouse paths, typing rhythm and scrolling - 13 mini_browser_* actions; every result carries a fresh page observation Password vault: Fernet + Windows DPAPI in .credentials/, atomic writes, no silent data loss, strict origin matching; autofill only for the current page's origin and never exposed to the model, logs or UI. Platform: mini_browser_* WebSocket protocol and lanes, new session type mini_browser, settings section, error codes, AgentBase run-state / stop / delete / sub-agent / shutdown hooks, skill, prompt rule and AGENT.md. Frontend: Mini Browser page (sidebar entry, live view with IME/paste/ shortcuts, tabs, take control, passwords dialog), 7 languages.
…ngs) Engine - shared start/close/recovery tasks are awaited without inheriting their cancellation; a close during a launch aborts at a phase boundary, keeps the profile lock until Chromium exits and leaks no driver - every agent tab gets its own Chromium window, so background owners are no longer throttled (clicks went from ~25 s to ~1 s) - ops race against a per-tab closed event: a closed tab or browser ends the op with MINI_BROWSER_TAB_CLOSED / MINI_BROWSER_CLOSED, never a fake success - run states and Stop are kept process-wide, so Stop before the first browser use still revokes sub-agents; the host is terminal after shutdown - agents see only their own tabs' URLs; user tabs are claimed only when blank, by the Mini Browser chat for the viewed tab, or by an explicit switch to the viewed tab - UI-origin blocking covers IPv4-mapped / 0.0.0.0 / [::] spellings; secrets are masked in their URL/HTML/JSON-encoded forms too - downloads get Mark-of-the-Web on Windows and a "dangerous" flag Agent operations - take-control stops in-flight typing/mouse/scroll at once - date/time inputs, framework click handlers, slotted labels, row context, iframes, modal text, slow-XHR settling and results kept under 16k chars - conservative login outcomes; CAPTCHA/2FA flow via wait(for_user) - vault autofill limited to the exact site (with or without www.) Platform - managed installs receive newly shipped action files and skills on upgrade (app/state_sync.py), without overwriting user edits - the Mini Browser prompt rule is shown only when the browser is usable - full /reset keeps per-session streams; input/control/shutdown get their own WebSocket lanes with input coalescing; settings reload live Frontend - no one-click Open for executable downloads; address bar binds to the edited tab; pending input never follows a view switch; passwords dialog error states; contrast, layout and translation fixes
ahmad-ajmal
left a comment
There was a problem hiding this comment.
I reproduced the six issues below with real browser tests and isolated storage. End-to-end checks used the production Mini Browser frontend and backend with a stub agent controller. Browser testing used Playwright 1.58 and installed Edge because the downloaded Chromium builds could not launch on this machine.
ahmad-ajmal
left a comment
There was a problem hiding this comment.
Four additional maintainability comments. The adapter contract and BrowserCore decomposition comments are follow-up suggestions.
- Agent input never lands after the user takes control. Waits for an element run in 250 ms slices that check control; clicks press through page.mouse at a point just checked to hit the element instead of Playwright's click, which kept waiting inside the driver. Focus, clear, caret, select, upload and whole-value inputs, and the vault login's fill and Enter, act on element handles right after the control check. - Agents cannot close a tab the user controls. - Opening a tab reserves its slot before the first await, so concurrent opens never exceed the browser-wide or per-owner tab limits. - Agent screenshots cover password and secret fields, including fields that were password fields before a "show password" toggle. - A saved password in a download's suggested name is masked before it becomes the file name, a log line or a notice. - Cut deletes the selection only after its own copy reached the clipboard, in the tab it was copied from (copy replies echo requestId). - One settings normalizer (mini_browser.config.clean_setting) for app.config and MiniBrowserSettings. - Browser tests skip only when Playwright or Chromium is not installed; CRAFTBOT_REQUIRE_BROWSER_TESTS=1 makes that a failure too.
ahmad-ajmal
left a comment
There was a problem hiding this comment.
Re-review of ccf5237: the original reproductions now pass, including the slow Ctrl+A/Ctrl+X production UI scenario. These three additional findings require specific timing conditions. I deliberately created the race windows to confirm the behavior; this does not establish how frequently users would encounter it. The dropdown issue is medium severity.
Threads (and other sites that link addresses as you type) put agents into
a delete-and-retype loop, with link previews flipping between sites:
- Web and e-mail addresses are inserted whole, like a paste. Typed key by
key, the site saw every partial address ("https://e", ...,
"example.co") and previewed the wrong ones.
- After typing into a rich text box (contenteditable), the box is read
back and the result says whether it holds the whole text; links, link
previews and styled mentions are named as the site's formatting.
- The same text is typed into the same page at most 3 times in 10
minutes; the 4th attempt is refused with a "stop and check or ask the
user" message. Only a hash of the text is kept.
- The mini-browser skill and the type action describe link previews and
say never to delete and retype a post because it looks different.
Sound: Playwright starts Chromium with --mute-audio, so no page could play
sound. New setting mini_browser.sound (default true) drops that switch; it
applies at the browser's next start.
The compact page observation showed only the first 40 characters of a post or message box, so a post with a link often appeared as "https://example.c…": a broken-looking link the agent took for its own typo, one more trigger of the Threads retype loop. - Textareas and rich text boxes show up to 300 characters, cut between words (also in the tighter fallback format), and say how long the full text is when it is cut. - The read-back after typing covers the whole editor, also when the element is one of its paragraphs. - The retype guard counts a retry that only writes an address differently ("https://" added or dropped) as the same text. Checked against the real Lexical editor with its AutoLinkPlugin: typing an address key by key changed the link five times (example.c -> example.co -> example.com -> ...), now it is created once. Sound was measured in the Windows mixer: Chromium plays (live-view and agent clicks) with sound on, and is silent with it off.
- Dropdowns: Playwright's select_option keeps waiting in the driver while the option is missing (force does not stop that retry), so a choice could land after the user took control. Each attempt is now one in-page step that finds the option again and selects it only if it is there and enabled (input + change, like Playwright); between attempts the wait only reads, and every attempt checks control first. - Closing the last tab: the owner and take-control checks run again after the replacement tab has opened, right before the close. - Cut: the copy is marked as a cut and the backend keeps the text it copied (memory only, 30 s). The delete is a "cut" input event in the ordered input queue, after whatever the user did meanwhile, and runs only while the selection is still exactly that text; otherwise it is skipped, so nothing that is not on the clipboard is deleted.
ahmad-ajmal
left a comment
There was a problem hiding this comment.
Consolidated review of d9800ff: four requests to address before merge. Each inline comment includes the condition and reproduction. Controlled timing tests establish possible behavior, not production frequency. The earlier reported scenarios now pass.
- Cut: only a complete copy can be cut. A selection longer than the copy limit, or one with a saved password masked out, reaches the clipboard only in part, so it is copied but never deleted; the delete compares the whole selection. Pending cuts are kept per request, with ids scoped to the viewer connection (and unique per view mount), so two viewers' cuts never share a record and a mismatched delete leaves the others intact. - Retype guard: scoped to the same field (data-mb-field) and to text that was not sent; Enter or the page moving on resets it. Addresses only ignore "https://" and "www.", so different links are different texts. Filling several fields with the same value is never refused. - Navigation: control is checked again right before an operation runs (after crash recovery and bringing its window forward), right before navigate's goto / back / forward / reload, and before a new tab opened with a URL loads it.
Adds the Mini Browser: a real Chromium that CraftBot's agents drive while the user watches live in a new sidebar page, and can take over at any moment. It replaces the earlier "Web Agent" prototype, rebuilt to fit current
main(sessions, i18n, provisioning, WebSocket lanes).Commits: the first imports the old prototype onto
main, the next two replace it, the fourth and the last two address the review comments, and the fifth and sixth fix a retype loop on Threads and let pages play sound. Review the branch as a whole, not commit by commit.What users get
mini_browser.sound: false.How it works
app/mini_browser/is a new package that is import-light: it never imports Playwright at import time.mini-browserthread with its own event loop owns Playwright. Every caller marshals in viahost.call(), so it works from the main loop and from sub-agents, which run on their own loops._session_id. Without separate windows, background tabs are throttled to about 1 s per input event.Network.setBlockedURLs.context.routeis not used because it disables the HTTP cache.app/data/action/mini_browser_actions.pyholds 13 thinmini_browser_*stubs: navigate, read, click, type, press_key, select_option, hover, scroll, wait, upload_file, login, screenshot and tabs. Every result carries a fresh, compact page observation, so agents need fewer LLM round-trips.mini_browser_*messages are handled inapp/mini_browser/ws.py. Navigation, input, control and the vault each get their own lane, so a frozen page can't block "Take control".Every kind of agent can use it
mini_browser), scheduled runs, Agent App trigger runs, and a newbrowser_agentsub-agent type.mini-browserskill;Password vault
.credentials/(0700 dir, 0600 files).www.), over https or on localhost, and only for the current page's origin.Changes outside
app/mini_browser/(please review closely)app/agent_base.py/resetnow recreates the streams of surviving sessions. The Mini Browser chat can be cleared but not deleted.run.py,app/state_sync.pyagent_core/core/impl/action/router.pyprompt_filterhook, used to hide the Mini Browser rule when the browser is unavailable.agent_core/core/session/session.py,impl/session/manager.pymini_browser.agent_core/core/prompts/action.py,context.pyapp/internal_action_interface.pyadd_action_setsmatches names loosely (-/_, case) and reports unknown sets with hints instead of silently succeeding.app/ui_layer/adapters/browser_adapter.py,ws_lanes.pyapp/config.py,app/errors/codebook.pymini_browsersettings section and the Mini Browser error codes.skills/playwright-mcp/SKILL.mdbrowser_*tool names collided with the new actions, so it now uses the realmcp_playwright-mcp_*names and points user-facing browsing to the Mini Browser..gitignore,.dockerignoreminiBrowserslice; resource; UI state;ToastContextgains optional action buttons;ActivityBlockspreviews formini_browser_*actions; a small onboarding fix so long error text wraps.Testing
main(tests/llm/golden,test_installer_stream_routing,test_prompt_profile) and the stale macOS shortcut test intest_craftbot_service.CRAFTBOT_REQUIRE_BROWSER_TESTS=1makes a missing browser a failure too.ruff format --check .andruff check .are clean (ruff 0.16.1, same as CI), andcompileallpasses.tsc --noEmitreports 0 errors, the productionvite buildsucceeds, and the i18n keys match across all 7 locales.mainfor these paths, so all of the above was run locally on Windows (Python 3.10, Playwright 1.58 / Chromium 145).How to try it
run.pyrebuilds the frontend).If Chromium isn't installed (it is an optional provisioning stage), the page offers an Install browser button.
Known limits / follow-ups
web_fetch.py). This predates this PR.scripts/package_source.pyleaves out the AGENT.md template. This also predates this PR.example.comno longer fills on other subdomains. A per-login "also use on subdomains" option would need a UI decision.BrowserCore's browser lifecycle (launch, recovery, teardown) from tab registry and ownership;CRAFTBOT_REQUIRE_BROWSER_TESTS=1.