Skip to content

feat: add multi-tab support to WebAgent#295

Merged
philipph-askui merged 3 commits into
mainfrom
feat/web-agent-tab-switching
Jul 16, 2026
Merged

feat: add multi-tab support to WebAgent#295
philipph-askui merged 3 commits into
mainfrom
feat/web-agent-tab-switching

Conversation

@philipph-askui

Copy link
Copy Markdown
Contributor

Summary

  • PlaywrightAgentOs now tracks all open browser pages via a _pages list and a context.on("page", ...) listener that fires whenever a new tab is opened (e.g. target="_blank" links, window.open())
  • New auto_follow_new_tab parameter (default True) controls whether newly opened tabs automatically become active; set to False to switch manually
  • Three new tools give the LLM full tab management: list_tabs, switch_tab, close_tab
  • WebAgent exposes auto_follow_new_tab as a constructor parameter and includes the three new tools in its default tool set
  • System prompt updated to reflect multi-tab capability

Changed files

File Change
tools/playwright/agent_os.py _pages list, _on_new_page handler, list_tabs / switch_tab / close_tab methods, auto_follow_new_tab param
tools/playwright/tools.py PlaywrightListTabsTool, PlaywrightSwitchTabTool, PlaywrightCloseTabTool
web_agent.py auto_follow_new_tab param, import + register new tools in get_default_tools()
prompts/act_prompts.py Updated WEB_BROWSER_CAPABILITIES and WEB_AGENT_DEVICE_INFORMATION to mention tab management

Test plan

  • Unit tests pass (pdm run test:unit — 647/647 ✅)
  • WebAgent with auto_follow_new_tab=True (default): click a target="_blank" link → agent automatically follows the new tab
  • WebAgent with auto_follow_new_tab=False: click a target="_blank" link → active tab stays; call list_tabs to confirm new tab is tracked; call switch_tab(1) to move to it
  • close_tab on the last remaining tab raises RuntimeError
  • close_tab on the active tab switches focus to an adjacent tab

🤖 Generated with Claude Code

philipph-askui and others added 3 commits July 16, 2026 11:06
Track all browser pages in PlaywrightAgentOs via a `_pages` list and a
`context.on("page")` listener that captures every new tab as it opens.
New tabs automatically become the active tab when `auto_follow_new_tab=True`
(the default); set it to `False` to keep the current tab active and switch
manually.

Three new tools let the LLM manage tabs autonomously:
- `list_tabs` — returns index, title, and URL for every open tab
- `switch_tab` — makes the tab at the given index active
- `close_tab` — closes the tab at the given index (guards the last tab)

`WebAgent` exposes `auto_follow_new_tab` as a constructor parameter and
includes the three new tools in its default tool set. The system prompt is
updated to reflect multi-tab capability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented auto-follow from working:

1. bring_to_front() deadlock: the _on_new_page callback runs in
   Playwright's background asyncio thread. Calling sync Playwright
   methods from that context deadlocks the greenlet dispatcher. Removed
   bring_to_front() from the handler; only the thread-safe
   self._page = page assignment is kept.

2. Race condition: the background-thread event fires after the main
   thread has already moved on to the next call (e.g. screenshot()),
   so self._page isn't updated in time. Added _sync_pages(), which
   is called at the start of every screenshot() from the main thread.
   It polls context.pages (safe from main thread), picks up any pages
   the event handler missed, and calls bring_to_front() safely there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous fix removed bring_to_front() from the event handler but
left a timing race: the "page" event only fires when the Playwright
event loop is pumped, which happens during page.screenshot(). By that
point the CDP screenshot command has already been sent to the OLD page,
so auto-follow had no effect on what the agent saw.

Fix: pump the event loop (wait_for_timeout(0)) at the start of
screenshot() BEFORE _sync_pages() is called. This forces the dispatcher
to process all queued events — including new-page events — so that by
the time the screenshot is taken, self._page already points to the new
tab.

Also introduce a _needs_bring_to_front flag. _on_new_page sets it
instead of calling bring_to_front() directly; _sync_pages() clears it
and calls bring_to_front() from the main greenlet after the pump. This
avoids silent error-deferral: any exception raised by a sync Playwright
call inside an EventGreenlet is stored in connection._error and re-raised
at the next API call, which could corrupt an unrelated operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@philipph-askui
philipph-askui merged commit 0877774 into main Jul 16, 2026
1 check passed
@philipph-askui
philipph-askui deleted the feat/web-agent-tab-switching branch July 16, 2026 11:12
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.

1 participant