diff --git a/packages/agentenv-protocol/src/agentenv_protocol/website_browser/control.py b/packages/agentenv-protocol/src/agentenv_protocol/website_browser/control.py index 8add4f5b..81666e83 100644 --- a/packages/agentenv-protocol/src/agentenv_protocol/website_browser/control.py +++ b/packages/agentenv-protocol/src/agentenv_protocol/website_browser/control.py @@ -617,8 +617,11 @@ def _on_Browser_downloadProgress(self, params: dict, session: Optional[str]) -> download["ended_at"] = _now() async def _hashed(self, download: dict, path: Path) -> None: - size, sha256 = await asyncio.to_thread(_size_and_sha256, path) - download.update(size=size, sha256=sha256, ended_at=_now()) + try: + size, sha256 = await asyncio.to_thread(_size_and_sha256, path) + download.update(size=size, sha256=sha256) + finally: + download["ended_at"] = _now() def _spawn(self, work: Awaitable) -> None: task = asyncio.ensure_future(work) diff --git a/packages/agentenv-protocol/src/agentenv_protocol/website_browser/shapes.py b/packages/agentenv-protocol/src/agentenv_protocol/website_browser/shapes.py index d4bfc8a1..74b6c675 100644 --- a/packages/agentenv-protocol/src/agentenv_protocol/website_browser/shapes.py +++ b/packages/agentenv-protocol/src/agentenv_protocol/website_browser/shapes.py @@ -97,7 +97,7 @@ class ClosedTab(BaseModel): class Download(BaseModel): - """A download into ``~/Downloads``; completed, with where it landed, its size and its sha256.""" + """A download into ``~/Downloads``; when completed, with where it landed, and its size and sha256 when available.""" guid: str url: str filename: str diff --git a/packages/agentenv-protocol/tests/test_website_browser.py b/packages/agentenv-protocol/tests/test_website_browser.py index 07f906d9..b9294f7c 100644 --- a/packages/agentenv-protocol/tests/test_website_browser.py +++ b/packages/agentenv-protocol/tests/test_website_browser.py @@ -909,6 +909,27 @@ async def test_its_control_records_a_completed_download_with_its_size_and_sha256 assert done["sha256"] == hashlib.sha256(b"%PDF-1.7 report").hexdigest() +@pytest.mark.asyncio +async def test_its_control_records_ended_at_when_download_hashing_fails(tmp_path): + devtools = StubDevTools() + control = BrowserControl(devtools, "agent") + missing_file = tmp_path / "nonexistent.pdf" + + devtools.send("Browser.downloadWillBegin", {"guid": "g2", "url": "https://a.test/missing.pdf", + "suggestedFilename": "missing.pdf"}) + devtools.send("Browser.downloadProgress", {"guid": "g2", "state": "completed", + "filePath": str(missing_file)}) + for _ in range(int(WAIT_S / POLL_S)): + if "ended_at" in control.downloads.get("g2", {}): + break + await asyncio.sleep(POLL_S) + + done = control.downloads["g2"] + assert done["state"] == "completed" + assert "ended_at" in done + assert "sha256" not in done + + @pytest.mark.asyncio async def test_its_control_says_the_tab_whose_page_has_focus_is_active_else_the_first_visible_one(tmp_path): states = {"s-a": ["hidden", False], "s-b": ["visible", False], "s-c": ["visible", True]}