Repository navigation
feat(website): live sites, browser/v1 sign-in and WARC replay (FD-4683) - #447
williamloo-scale wants to merge 24 commits into
Conversation
…-4683) @website(domains=[...], live=True, allow_hosts=[...]) declares a real site: its server serves no pages, no API and no built-in session or /go route, only health, the data plane and the card, which advertises live and allow_hosts. @session returns the cookies a browser adds to start signed in; website/v1's session checks them, gives a cookie without a domain the site's first, and answers no_session (501) for a live site without one. self.setting(name) reads a credential its deploy passes. agentenv_protocol.website.browser defines urn:agentenv:browser/v1 with cookies/set and cookies/clear, the Cookie shape, and redacted() for what a record keeps of one; cookie errors name an index and field, never a value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…FD-4683) python -m agentenv_protocol.website.replay capture URL... --out capture.warc.gz writes WARC 1.1 with the standard library and httpx: the start URLs, same-site links up to --depth, each page's assets, and redirects as answered. ReplayWebsite serves a capture for any host, matched by method, path and normalised query, 404 for a miss; @api routes win over the capture, the default store holds their state for the data plane, and data/add of a .warc(.gz) file part serves that capture until data/reset. The example replays a captured docs site with a synthetic sign-up. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…/set and cookies/clear (FD-4683) The recorder's card, the browser's child env, now serves browser/v1. Playwright MCP launches its browser at the agent's first tool call and starts each context from --storage-state, so cookies/set writes the cookies to that file (0600, merged by name, domain and path) and, through DevTools, into every context of a browser already running; cookies/clear removes those of some domains or all. Answers and errors never repeat a cookie's value. The recorder's protocol floor rises to 0.1.405, the first release with agentenv_protocol.website.browser. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reproducible, website env settings (FD-4683) deploy_env marks a deploy with a live website child: context.metadata reproducible=False and live_websites (each site's domains and allow_hosts, checked against the run's computer allowlists, which can't change once a sandbox runs, so a host one leaves out is recorded and warned about), and the deploy record live. The sign_in_browser step calls each live site's website/v1 session and passes the cookies straight to the browser child's browser/v1 cookies/set; the context, logs and errors keep cookies by name, domain, path and expiry only. reset_env signs in again after a reset. A website env's settings are its container's environment variables: a secret is stored as a secret:/env: reference and resolved only into the compose file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ation by keyword (FD-4683) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ic site's card is its spec's; tests follow the session cookie and invoke's headers (FD-4683) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
…s sign_in_browser sets (FD-4683) main's WebsiteBrowserEnvironment (#449) declares urn:agentenv:browser/v1 with its build, clock and window; this branch's cookies/set and cookies/clear are operations of the same extension. A test keeps the two constants one URI. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/agent_env/env/envs/website.py # src/agent_env/providers/env_providers/env_gateway_provider.py
…s container, whatever its value holds (FD-4683) Each setting is written as one double-quoted YAML scalar, its quotes, backslashes and non-printable characters escaped and each $ doubled, so a newline can't add service keys like privileged or volumes and compose doesn't interpolate it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and models it serves now (FD-4683) Whether /api/session and /go answer from the capture is decided per request against the archive being served, so a capture loaded or reset through the data plane is matched as the declared one. data/add and data/reset hand what isn't a capture to the base hooks, so a models replay loads and resets its database. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t its hosts too (FD-4683) mark_live makes the network check of every live site recorded so far again on each deploy, with the env being deployed among the run's computers, so a website-first run records the allowlist that leaves a site's hosts out instead of unrestricted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
# Conflicts: # packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
… by the site's own path (FD-4683) The gateway's X-Forwarded-Prefix leads the scope's path and raw_path, so the replay searched the capture by /website/<name>/... and answered 404. The replay and the captured built-in routes now match the path without the root_path, keeping its escaped segments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
compose_env_entry writes a setting as a quoted "NAME=VALUE" entry, which the secret-key mask didn't match, so a resolved token reached the deploy log. Every quoted setting line is now masked whatever its name, since a secret: or env: reference may resolve to a credential under any name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ugin's deployment sends (FD-4683) data/add of a capture took only its bytes or a file:// URI, so a load through a plugin's provider, which sends each file as a signed URL, failed after its data/reset. An https capture is now fetched, up to MAX_CAPTURE_URL_BYTES, and an error names its status, never the signed URL. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s under it (FD-4683) A redirect to a captured host, and a root-relative one, are led by the request's root_path, so a replay opened at /website/<name>/ redirects to /website/<name>/new rather than the gateway's /new. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
BrowserCookies holds a lock across each set or clear, its file write and DevTools calls together, so concurrent clears for different sites, or a set during a clear, never restore or erase what the other changed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
| def _answer(self, entry: Entry, head: bool, root: str = "") -> Response: | ||
| response = Response(b"" if head else entry.body, status_code=entry.status) |
There was a problem hiding this comment.
_answer sends captured HTML unchanged. When a page opens at /website/shop/, a link to /style.css goes to the gateway root, while https://shop.example/... goes back to the real site. Navigation and asset loads therefore bypass the replay.
Rewrite same-site links and asset URLs for the address serving the replay. Rewriting Location alone does not cover page content.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/replay.py
Line: 485-486
Comment:
**Links leave the replay**
`_answer` sends captured HTML unchanged. When a page opens at `/website/shop/`, a link to `/style.css` goes to the gateway root, while `https://shop.example/...` goes back to the real site. Navigation and asset loads therefore bypass the replay.
Rewrite same-site links and asset URLs for the address serving the replay. Rewriting `Location` alone does not cover page content.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if is_asset or "html" not in response.headers.get("content-type", ""): | ||
| return |
There was a problem hiding this comment.
_next stops at each stylesheet, so images and fonts referenced by CSS are never saved. A page using background-image: url('/image.png') captures its stylesheet but gets a 404 for the image during replay, even with asset capture enabled.
Follow stylesheet imports and CSS asset URLs within the existing host and request limits.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/replay.py
Line: 327-328
Comment:
**CSS assets are missing**
`_next` stops at each stylesheet, so images and fonts referenced by CSS are never saved. A page using `background-image: url('/image.png')` captures its stylesheet but gets a 404 for the image during replay, even with asset capture enabled.
Follow stylesheet imports and CSS asset URLs within the existing host and request limits.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| raw = source if isinstance(source, bytes) else Path(source).read_bytes() | ||
| if raw[:2] == b"\x1f\x8b": | ||
| raw = gzip.decompress(raw) | ||
| return cls(_entries(io.BytesIO(raw)), ignore_query) |
There was a problem hiding this comment.
Capture expansion exhausts memory
Archive.read expands the entire gzip file before checking its contents or size. The download limit only bounds the compressed capture. A highly compressed archive can pass that limit and exhaust the website container's memory during data/add.
Bound the expanded bytes while decompressing, and apply the same limit to inline and local-file captures.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/replay.py
Line: 128-131
Comment:
**Capture expansion exhausts memory**
`Archive.read` expands the entire gzip file before checking its contents or size. The download limit only bounds the compressed capture. A highly compressed archive can pass that limit and exhaust the website container's memory during `data/add`.
Bound the expanded bytes while decompressing, and apply the same limit to inline and local-file captures.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Stacked on #446.
Live and replayed websites.
@website(domains=[...], live=True, allow_hosts=[...])declares a real site: its server serves only health, the data plane and the card (which advertisesliveandallow_hosts).@sessionreturns the cookies a browser adds to start signed in;self.setting(name)reads a credential the deploy passes.urn:agentenv:browser/v1):cookies/setandcookies/clear, operations of the same extension main'sWebsiteBrowserEnvironment(feat(computer): WebsiteBrowserEnvironment, the base class for website browsers #449) declares with its build, clock and window;sign_in_browserpicks a browser that offerscookies/set, so a website browser joins once it serves them (M5.11). The Playwright MCP browser serves them, writing its storage state and, through DevTools, any running context. Answers and errors never repeat a cookie's value.sign_in_browserstep. Calls each live site's session and hands the cookies to the browser;reset_envsigns in again. A deploy with a live site is markedreproducible=Falsewith itslive_websites, checked against the computer's allowlist.settingsare its container's environment variables. A secret-looking setting must be asecret:/env:reference, resolved only into the compose file. The gateway's own variables, the role key among them, can't be set.python -m agentenv_protocol.website.replay capture URL...writes WARC 1.1;ReplayWebsiteserves a capture for any host, with@apiroutes taking precedence. Example inexamples/website/replay/.Before merging
website.browser, so it needs that release.website.sessionis now the decorator, which shadows the module.Test
Unit suite: 12,870 passed (one GitHub-clone test from main, which hangs on the local machine on main too, deselected).
🤖 Generated with Claude Code
Fix captured page links before merging; otherwise navigation and asset loads can leave the replay through gateway URLs.
Fix with agent prompt
Summary
Adds live website declarations, browser cookie sign-in, website settings, and WARC capture and replay.
Diagram
Reviews (4) · Last reviewed commit: "Merge wloo/website-authoring-cli into wl..." · Reviewed by Greptile