Skip to content

files download [WIP]#70

Closed
volodymyr-narada wants to merge 8 commits into
mainfrom
volodymyr/cdp_files_download_v2
Closed

files download [WIP]#70
volodymyr-narada wants to merge 8 commits into
mainfrom
volodymyr/cdp_files_download_v2

Conversation

@volodymyr-narada

@volodymyr-narada volodymyr-narada commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Note

Medium Risk
Introduces new CDP event handling and streaming file transfer for cloud browser sessions; failures could leak resources (contexts/sessions) or miss/incorrectly transfer downloads, but changes are scoped to cloud-browser flow.

Overview
Adds first-class download support for cloud browser sessions by wiring a browser-level CDPDownloadHandler into open_and_initialize_cloud_browser_window, capturing downloads across tabs and optionally invoking a user-provided BrowserConfig.on_download_complete callback.

Introduces narada.cloud_downloads with DownloadInfo, download tracking (wait_for_download/wait_for_all), and chunked remote-to-local streaming via CDP Fetch + IO.read, and exposes this on CloudBrowserWindow (has_pending_downloads, transfer helpers). Also exports DownloadInfo from the package and ensures CloudBrowserWindow.close() disconnects the Playwright browser connection before stopping the cloud session.

Written by Cursor Bugbot for commit 89a6126. This will update automatically on new commits. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 5 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

break
if deadline is not None and asyncio.get_event_loop().time() >= deadline:
return None
await asyncio.sleep(0.5)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed downloads never returned

Medium Severity

wait_for_download only targets entries in inProgress state. If a download already finished before this call, it is skipped and the method polls until timeout, returning None despite completed data in self._downloads. This makes CloudBrowserWindow.wait_for_download unreliable for normal post-action usage.

Fix in Cursor Fix in Web

Comment thread packages/narada/src/narada/window.py Outdated

async def transfer_all_downloads(
self,
local_dir: str | Path = "/Users/Volodymyr/projects/narada/remote_downloads",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default download directory is machine-specific

Medium Severity

transfer_all_downloads defaults local_dir to an absolute developer path (/Users/Volodymyr/...). On most environments this path is invalid or not writable, so calling transfer_all_downloads() with defaults can fail before transfer starts.

Fix in Cursor Fix in Web

Comment thread packages/narada/src/narada/window.py Outdated
# Disconnect Playwright from the browser (does not stop the remote session).
if self._browser is not None:
try:
self._browser.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browser close coroutine not awaited

Medium Severity

CloudBrowserWindow.close calls self._browser.close() without await even though this is the Playwright async API. The coroutine may never run, leaving the CDP connection open and causing resource leakage or cleanup races during session shutdown.

Fix in Cursor Fix in Web

"""
if not self._downloads:
print("[cloud_downloads] No downloads were captured")
return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Early return misses in-flight download events

Medium Severity

wait_for_all returns immediately when self._downloads is empty, so transfer_all_downloads can miss downloads that start moments later. This creates a race where post-action transfers report no files even though a download was triggered and completes shortly after.

Additional Locations (1)

Fix in Cursor Fix in Web

Comment thread packages/narada/src/narada/window.py
@volodymyr-narada volodymyr-narada marked this pull request as draft February 16, 2026 19:48
@volodymyr-narada volodymyr-narada marked this pull request as ready for review February 18, 2026 00:03
Comment on lines +164 to +170
loop = asyncio.get_running_loop()
loop.run_in_executor(
None,
lambda: self._on_download_complete(
self._session_id, guid, filename
),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if _on_download_complete is async

asyncio.create_task(_on_download_complete(...)) to create a background task

return local_path

except Exception as exc:
logger.exception("Transfer failed: %s", exc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.exception("Transfer failed: %s", exc)
logger.exception("Transfer failed")

current exception is automatically captured

extension_id: str = "bhioaidlggjdkheaajakomifblpjmokn"
interactive: bool = True
proxy: ProxyConfig | None = None
on_download_complete: Callable[[str, str, str], None] | None = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can/should this be async? see question above about why we are alternating between blocking and async functions

logger.warning("Browser reference not available -- cannot transfer file")
return None

from narada.cloud_downloads import download_remote_file_to_local

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we importing in the function not at the top of the file

def cloud_browser_session_id(self) -> str:
return self._session_id

async def wait_for_download(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it private

return None
return await self._download_handler.wait_for_download(timeout=timeout)

async def transfer_download(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

self._browser, download.remote_path, local_path
)

async def transfer_all_downloads(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make private

Comment on lines +758 to +759
local_download_dir: str | Path | None = None,
download_wait_timeout: float = 60.0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove these

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants