-
Notifications
You must be signed in to change notification settings - Fork 1.6k
daemon: give each named daemon its own tab on shared browsers #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -360,12 +360,30 @@ def __init__(self): | |
| self.cdp = None | ||
| self.session = None | ||
| self.target_id = None | ||
| self.owns_target = False # True when this daemon created its own tab | ||
| self.events = deque(maxlen=BUF) | ||
| self.dialog = None | ||
| self.stop = None # asyncio.Event, set inside start() | ||
|
|
||
| async def attach_first_page(self): | ||
| """Attach to a real page (or any page). Sets self.session. Returns attached target or None.""" | ||
| # Named daemons (BU_NAME != "default") share one browser with other | ||
| # daemons — attaching to the first page makes parallel daemons fight | ||
| # over a single tab (navigations clobber each other). Give each named | ||
| # daemon its own dedicated tab instead. REMOTE_ID (cloud) browsers are | ||
| # already exclusive to this daemon, so first-page attach stays. | ||
| if NAME != "default" and not REMOTE_ID: | ||
| tid = (await self.cdp.send_raw("Target.createTarget", {"url": "about:blank"}))["targetId"] | ||
| self.owns_target = True | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: If target creation succeeds but attach or startup setup fails, the daemon exits before its cleanup block and leaves an orphan tab in the shared browser. Record the created ID immediately and clean it up from the attach/startup failure path. Prompt for AI agents |
||
| log(f"named daemon {NAME}: created dedicated tab ({tid})") | ||
| page = {"targetId": tid, "url": "about:blank", "type": "page"} | ||
| self.session = (await self.cdp.send_raw( | ||
| "Target.attachToTarget", {"targetId": tid, "flatten": True} | ||
| ))["sessionId"] | ||
| self.target_id = tid | ||
| log(f"attached {tid} (about:blank) session={self.session}") | ||
| await self._enable_default_domains(self.session) | ||
| return page | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When startup follows the Chrome remote-debugging recovery flow, this early return skips inspect-tab cleanup and marker removal. Run the same cleanup for the named local path before returning. Prompt for AI agents |
||
| targets = (await self.cdp.send_raw("Target.getTargets"))["targetInfos"] | ||
| pages = [t for t in targets if is_real_page(t)] | ||
| if not pages: | ||
|
|
@@ -615,7 +633,21 @@ async def handler(reader, writer): | |
| async def main(): | ||
| d = Daemon() | ||
| await d.start() | ||
| await serve(d) | ||
| try: | ||
| await serve(d) | ||
| finally: | ||
| # A named daemon owns the tab it created — close it on shutdown so | ||
| # parallel workers don't leak about:blank/leftover tabs into the | ||
| # shared browser. Best-effort: the WS may already be gone. | ||
| if d.owns_target and d.target_id: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: After a named daemon switches tabs, Prompt for AI agents |
||
| try: | ||
| await asyncio.wait_for( | ||
| d.cdp.send_raw("Target.closeTarget", {"targetId": d.target_id}), | ||
| timeout=2, | ||
| ) | ||
| log(f"closed owned tab {d.target_id}") | ||
| except Exception as e: | ||
| log(f"close owned tab {d.target_id}: {e}") | ||
|
|
||
|
|
||
| def already_running(): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: On a stale-session re-attach, a named daemon creates a brand-new dedicated tab and overwrites
owns_target/target_id, so the previous tab it created inattach_first_page()is never closed. Only the last tab is closed at shutdown, so each re-attach leaks oneabout:blankinto the shared browser. Track the previous owned target andTarget.closeTargetit (best-effort) before re-creating a tab whenself.owns_target and self.target_idis already set.Prompt for AI agents