Repository navigation
feat(website-browser): browser/v1 reads and sets cookies and local storage and returns downloads - #509
Merged
earakely-scale merged 5 commits intoNov 6, 2026
Conversation
…ipe, its control and its environment devtools.py holds the CDP pipe, control.py BrowserControl and its record, environment.py WebsiteBrowserEnvironment and its settings; the package re-exports every name it had, so no import changes. Tests that patch a module global patch it where it's defined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orage and returns downloads, its record as models cookies/get|set|delete and local_storage/get|set|clear act on the default context in the shape of Playwright's storageState; a max_age becomes an expiry on the computer's clock, and local storage goes through a hidden page agentenv serves itself, so the site gets no request. downloads/list|get return a download with its file, uploaded under a write_namespace grant or inline. The record's entries are pydantic models, every field present, and tabs name their opener. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a download started from the page Calls at once each get their answer, agentenv's own pages never show in the record, the operations work again after the app server is killed and comes back, and with the browser killed they fail at once. The download is started from a page, since a navigation that becomes a download is aborted; the browser card lists the new methods. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rigins as the browser names them, cookies set at once with their partition The hidden page that reaches an origin's local storage bypasses the origin's service worker, which otherwise answered its navigation and ran the site's script, and takes the origin as the browser names it once the page is there. Cookies are set in one call, which the browser applies all or none of, and keep a partitioned cookie's partitionKey on the way out, in, and to its deletion. A cross-site iframe's partitioned local storage isn't read, as storageState doesn't. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… asked whether it reached its origin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale
deleted the
edgararakelyan/fd-4940-m511-browserv1-over-cdp-cookies-local-storage-tabs-and
branch
November 6, 2026 00:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
agent-env can now read and set a website browser's cookies and local storage, and read its downloads, through typed
browser/v1operations over CDP. M5.13's data plane (sign-in throughload_artifact, browser state insnapshot_envandreset_env) and M5.14's browser task steps are both built on these operations. This is FD-4940 (M5.11). Its outcome and four decisions are recorded there.The operations are framework-only, like the rest of
browser/v1: reached through the computer driver's/svc/chromium/, behind the control token, and never an agent tool. They work the same in a desktop and on the browser computer alone. All of them act on the default context; contexts come with M8.15.Commit 1, the split.
website_browser.py(984 lines) becomes a package:devtools.py, the CDP pipe;control.py,BrowserControland its record;environment.py,WebsiteBrowserEnvironmentand its settings.The package re-exports every name it had, so no imports change. Git shows it as a delete plus new files; the code moved unchanged. Two tests that patch a module global now patch the submodule that defines it.
Commit 2, the operations and the shapes.
storageState, which uses CDP'sNetwork.Cookienames (httpOnly,sameSite,expires), so a storage state exported from Playwright loads as is. agentenv's own records keep the snake_case names main already ships, which the page driver, the relay and the gateway's events use.shapes.pydefines them as pydantic models:OpenTab,ClosedTab,Download,Dialog,LaunchEntryandBrowserRecord. Tabs gainopener, the tab that opened a popup. Every field is now present in the record, null until it's known.cookies/get {urls?},cookies/set {cookies}andcookies/delete {name?, domain?, path?}(decision 4).getreturns the cookies a request to any ofurlswould send, as Playwright picks them, or every cookie.settakes aurl, or adomainandpath(/by default). A cookie may carrymax_agein place ofexpires, which becomes an absolute expiry on the computer's clock, the one Chrome judges cookies by. A cookie with both is refused. All the cookies go in oneStorage.setCookiescall, which Chrome applies all or none of, so a refusal means none was set.deleteremoves every cookie matching all the given fields, by its exact domain, and says how many.partitionKey, on the way out, on the way back in and to its deletion. Without the key, Chrome makes a second, unpartitioned cookie, andNetwork.deleteCookiesleaves the partitioned one in place.local_storage/get {origins?},local_storage/set {origins}andlocal_storage/clear {origins?}(decision 2). CDP reaches an origin's local storage only through a frame of that origin. In Chrome 156,DOMStorageby storage key or origin answers "Frame not found for the given storage id" otherwise. So each operation sends a hidden page of agentenv's own to the origin and answers its request withFetch.fulfillRequest: the site gets no request and runs no script.Network.enable, thenNetwork.setBypassServiceWorker). Otherwise a registered worker answers the navigation itself and runs the site's script there.https://bücher.testcomes back ashttps://xn--bcher-kva.test.origins, it covers every origin a page or same-site frame has loaded since its server started, plus those agentenv set.storageStatedoesn't read it either.other. Hiding them is FD-5277.downloads/listanddownloads/get {guid, write_namespace?}(decision 3).getreturns a download and, once completed, its file throughfile_parts, asdata/getreturns files: uploaded under the caller'swrite_namespacegrant when there is one, else inline. Inline, the whole file is read into memory, asdata/getdoes today, so a large one belongs under a grant. A grant that doesn't validate is reported by its field names only.Later commits test the operations under chaos (commit 3) and apply the review's fixes: the service-worker bypass, origins as the browser names them, one
setCookiescall, and the partition key (commits 4 and 5). Those decisions are on FD-4940.Not in this PR:
contextargument (M8.15);data/add|get|resetbuilt from these operations (M5.13);How it was tested
ruff check: clean.Unit tests:
test_browser_state.py, 12 tests against a stub browser:storageStateshape and are picked for URLs as Playwright picks them (domain, path, secure, local hosts);max_agebecomes the expiry on a frozen clock;Protocol suite: 1230 passed. Environment, driver, task step and provider unit tests: 6072 passed. The 4 failures there (
cua_driver_testandtest_disk_recipe) fail the same on main on macOS.VM e2e, agent-environments-admin, amd64 and arm64:
browser_state.check_browser_state, a new real-Chrome check, runs on chromium in a desktop (chromium_app_test) and on the browser computer alone (browser_computer_test), against a site served in the computer:cookies/setwithmax_age: 3600gives an expiry within 120 s of an hour after the computer's clock, and anhttpOnlycookie is set by domain;local_storage/setbefore the site is ever visited leaves the site with 0 requests;Cookieheader, sees only the non-httpOnlyone indocument.cookie, and reads the item from local storage;local_storage/getwith no origins includes the site;cookies/delete {name}deletes 1 andlocal_storage/clearempties the origin, and the next page load has neither;cookies/get, the first with its key, andcookies/delete {name}deletes both;sw_ran.local_storage/getfor it returns nosw_ran, and the site gets no request. A visible tab then loads the worker's page and does getsw_ran, so the worker really was answering;downloads/getwith its bytes.A chaos test on the browser computer (
check_browser_state_under_chaos) checks that:local_storage/getcalls, acookies/get, alocal_storage/setand alocal_storage/clearsent at once each get their answer;The cookie and the item set before the server was killed were still there after it, on both arches. The test reports that but doesn't assert it, since it depends on what Chrome had written to its profile.
The 3 failures in the first run were all in the tests. Two were the new state check: Chrome aborts a navigation that becomes a download (
net::ERR_ABORTED), as Playwright'sgotodoes, so the check now starts the download from a page. The third waswebsite_browser_test's exact list ofbrowser/v1methods, which now has the new ones.aee9881 only names a constant. Separately, with Playwright and Puppeteer kept connected to the browser computer's CDP address, the state calls answered in 20–50 ms. Neither client counted agentenv's hidden pages, and both kept driving the browser.
The full chromium, browser computer, website browser and gateway CDP suites ran too. On arm64, the three echo-agent tests were left out, since their image needs qemu there.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge, with no new actionable findings.Summary
Adds framework-only
browser/v1operations for cookies, local storage, and downloaded files.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Caller[Framework caller] --> Browser["browser/v1"] Browser --> Cookies[Cookies through CDP] Browser --> Hidden[Hidden page served by agentenv] Hidden --> Storage[Local storage] Browser --> Downloads[Download record] Downloads --> Files[Inline file or granted upload]Reviews (3) · Last reviewed commit: "refactor(website-browser): name how ofte..." · Reviewed by Greptile