@mastra/browser-viewer 0.1.0 → 0.1.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -2
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @mastra/browser-viewer
|
|
2
2
|
|
|
3
|
+
## 0.1.1-alpha.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Remove unused `userDataDir` config option from `BrowserViewerConfig`. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
|
|
8
|
+
|
|
9
|
+
- Standardize `headless` default to `true` across all browser providers. Each provider now resolves `headless` once in its constructor and passes it to the thread manager via the base class getter, removing duplicate fallback logic. ([#15696](https://github.com/mastra-ai/mastra/pull/15696))
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`750b4d3`](https://github.com/mastra-ai/mastra/commit/750b4d3d8231f92e769b2c485921ac5a8ca639b9)]:
|
|
12
|
+
- @mastra/core@1.28.0-alpha.1
|
|
13
|
+
|
|
3
14
|
## 0.1.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -76,10 +76,9 @@ const workspace = new Workspace({
|
|
|
76
76
|
| ---------------- | -------------------------------------------------- | ---------- | ------------------------------------------------ |
|
|
77
77
|
| `cli` | `'agent-browser' \| 'browser-use' \| 'browse-cli'` | Required | Which CLI the agent uses |
|
|
78
78
|
| `cdpUrl` | `string` | - | Connect to existing browser instead of launching |
|
|
79
|
-
| `headless` | `boolean` | `
|
|
79
|
+
| `headless` | `boolean` | `true` | Run browser in headless mode |
|
|
80
80
|
| `cdpPort` | `number` | `0` (auto) | Port for Chrome remote debugging |
|
|
81
81
|
| `viewport` | `{ width, height }` | `1280x720` | Browser viewport size |
|
|
82
|
-
| `userDataDir` | `string` | - | Chrome profile directory |
|
|
83
82
|
| `executablePath` | `string` | - | Path to Chrome executable |
|
|
84
83
|
|
|
85
84
|
## How It Works
|
package/dist/index.cjs
CHANGED
|
@@ -141,7 +141,7 @@ var BrowserViewerThreadManager = class extends import_browser.ThreadManager {
|
|
|
141
141
|
const cdpPort = this.browserConfig.cdpPort ?? 0;
|
|
142
142
|
this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);
|
|
143
143
|
const launchOptions = {
|
|
144
|
-
headless: this.browserConfig.headless
|
|
144
|
+
headless: this.browserConfig.headless,
|
|
145
145
|
args: [`--remote-debugging-port=${cdpPort}`, "--no-first-run", "--no-default-browser-check"]
|
|
146
146
|
};
|
|
147
147
|
if (this.browserConfig.executablePath) {
|
|
@@ -538,7 +538,7 @@ var BrowserViewer = class extends import_browser2.MastraBrowser {
|
|
|
538
538
|
viewerConfig;
|
|
539
539
|
constructor(config) {
|
|
540
540
|
const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
|
|
541
|
-
const { cli: _cli, cdpPort: _cdpPort,
|
|
541
|
+
const { cli: _cli, cdpPort: _cdpPort, ...baseConfig } = config;
|
|
542
542
|
super({
|
|
543
543
|
...baseConfig,
|
|
544
544
|
scope: effectiveScope
|
|
@@ -548,7 +548,7 @@ var BrowserViewer = class extends import_browser2.MastraBrowser {
|
|
|
548
548
|
this.viewerConfig = config;
|
|
549
549
|
this.threadManager = new BrowserViewerThreadManager({
|
|
550
550
|
scope: effectiveScope,
|
|
551
|
-
browserConfig: config,
|
|
551
|
+
browserConfig: { ...config, headless: this.headless },
|
|
552
552
|
logger: this.logger,
|
|
553
553
|
onSessionCreated: (session) => {
|
|
554
554
|
this.notifyBrowserReady(session.threadId);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/browser-viewer.ts","../src/thread-manager.ts"],"sourcesContent":["/**\n * @mastra/browser-viewer\n *\n * Browser viewer for Mastra workspaces with CLI provider support.\n * Launches Chrome via Playwright and exposes CDP URL for CLI tools.\n */\n\nexport { BrowserViewer } from './browser-viewer';\nexport { BrowserViewerThreadManager } from './thread-manager';\nexport type { BrowserViewerConfig, CLIProvider } from './types';\nexport type { BrowserViewerThreadManagerConfig } from './thread-manager';\n","/**\n * BrowserViewer - Playwright-managed Chrome for CLI providers\n *\n * Launches Chrome via Playwright and exposes the CDP URL for CLI tools\n * (agent-browser, browser-use, browse-cli) to connect as secondary clients.\n *\n * This gives us:\n * - Direct page-level CDP sessions (fixes screencast sessionId issues)\n * - Full browser lifecycle control\n * - Predictable CDP URL for CLI injection\n * - Thread-scoped browser isolation\n */\n\nimport { MastraBrowser, ScreencastStreamImpl } from '@mastra/core/browser';\nimport type {\n BrowserState,\n BrowserTabState,\n ScreencastOptions,\n ScreencastStream,\n CdpSessionProvider,\n MouseEventParams,\n KeyboardEventParams,\n} from '@mastra/core/browser';\nimport type { Tool } from '@mastra/core/tools';\nimport type { Page } from 'playwright-core';\nimport { BrowserViewerThreadManager } from './thread-manager';\nimport type { BrowserViewerConfig, CLIProvider } from './types';\n\n/**\n * BrowserViewer - CLI provider with Playwright-managed Chrome\n *\n * Use this with Workspace to enable browser automation via CLI tools.\n * The agent uses skills + workspace_execute_command to drive the CLI,\n * while Mastra handles screencast, input injection, and lifecycle.\n *\n * @example\n * ```ts\n * import { Workspace } from '@mastra/core';\n * import { BrowserViewer } from '@mastra/browser-viewer';\n *\n * const workspace = new Workspace({\n * browser: new BrowserViewer({\n * cli: 'agent-browser',\n * headless: false,\n * }),\n * });\n * ```\n */\nexport class BrowserViewer extends MastraBrowser {\n override readonly id: string;\n override readonly name = 'BrowserViewer';\n override readonly provider = 'browser-viewer';\n readonly providerType = 'cli' as const;\n\n /** Which CLI the agent uses */\n readonly cli: CLIProvider;\n\n /** Viewer-specific config (stored for reference) */\n readonly viewerConfig: BrowserViewerConfig;\n\n /** Thread manager for browser sessions */\n declare protected threadManager: BrowserViewerThreadManager;\n\n constructor(config: BrowserViewerConfig) {\n // Default to 'thread' scope (each thread gets its own Chrome)\n // Use 'shared' if connecting to an existing browser\n const effectiveScope = config.cdpUrl ? (config.scope ?? 'shared') : (config.scope ?? 'thread');\n\n // Build base config (exclude CLI-specific options)\n // Use type assertion because BrowserConfig is a discriminated union\n const { cli: _cli, cdpPort: _cdpPort, userDataDir: _userDataDir, ...baseConfig } = config;\n\n super({\n ...baseConfig,\n scope: effectiveScope,\n } as any);\n\n this.id = `browser-viewer-${Date.now()}`;\n this.cli = config.cli;\n this.viewerConfig = config;\n\n // Initialize thread manager\n this.threadManager = new BrowserViewerThreadManager({\n scope: effectiveScope,\n browserConfig: config,\n logger: this.logger,\n onSessionCreated: session => {\n // Notify listeners so screencast can start for this thread\n this.notifyBrowserReady(session.threadId);\n },\n onBrowserCreated: (_browser, threadId, _cdpUrl) => {\n this.logger?.debug?.(`Browser created for thread ${threadId}`);\n },\n onBrowserClosed: threadId => {\n this.logger?.debug?.(`Browser closed for thread ${threadId}`);\n // Notify base class callbacks so ViewerRegistry gets notified\n this.notifyBrowserClosed(threadId);\n },\n });\n }\n\n // ---------------------------------------------------------------------------\n // CDP URL Access\n // ---------------------------------------------------------------------------\n\n /**\n * Get the CDP WebSocket URL for CLI tools to connect.\n * For thread scope, returns the CDP URL for the specified thread.\n * For shared scope, returns the single shared CDP URL.\n *\n * @param threadId - Thread identifier (optional, uses current thread if not specified)\n * @returns CDP URL or null if browser not running for that thread\n */\n override getCdpUrl(threadId?: string): string | null {\n return this.threadManager.getCdpUrlForThread(threadId ?? this.getCurrentThread());\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async doLaunch(): Promise<void> {\n const scope = this.threadManager.getScope();\n const cdpUrl = this.config.cdpUrl;\n\n if (cdpUrl) {\n // Connect mode: connect to existing browser (always shared)\n const url = typeof cdpUrl === 'function' ? await cdpUrl() : cdpUrl;\n await this.connectToExisting(url);\n } else if (scope === 'shared') {\n // Shared mode: launch single browser\n await this.threadManager.createSharedSession();\n }\n // For thread scope, browsers are launched lazily per thread via ensureReady()\n }\n\n protected override async doClose(): Promise<void> {\n await this.threadManager.closeAll();\n }\n\n /**\n * Connect to an existing browser via CDP URL.\n */\n private async connectToExisting(cdpUrl: string): Promise<void> {\n this.logger?.debug?.(`Connecting to existing browser at ${cdpUrl}`);\n\n // Create a shared session from the external CDP connection\n await this.threadManager.createSharedSessionFromCdp(cdpUrl);\n\n this.logger?.debug?.('Connected to existing browser');\n }\n\n /**\n * Ensure browser is ready for the current thread.\n * For thread scope, creates a new browser if needed.\n */\n override async ensureReady(): Promise<void> {\n const scope = this.threadManager.getScope();\n const threadId = this.getCurrentThread();\n\n // For thread scope, create browser for this thread if needed\n if (scope === 'thread' && !this.threadManager.isBrowserRunning(threadId)) {\n await this.threadManager.getManagerForThread(threadId);\n }\n\n await super.ensureReady();\n }\n\n /**\n * Check if browser is running (for current thread in thread scope).\n */\n override isBrowserRunning(threadId?: string): boolean {\n return this.threadManager.isBrowserRunning(threadId ?? this.getCurrentThread());\n }\n\n /**\n * Launch browser, optionally for a specific thread.\n * For thread scope, creates a browser for that thread.\n * For shared scope, launches the single shared browser.\n */\n override async launch(threadId?: string): Promise<void> {\n const scope = this.threadManager.getScope();\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n\n if (scope === 'shared') {\n // For shared scope, use base class launch (handles racing, status, etc.)\n if (!this.threadManager.isBrowserRunning()) {\n await super.launch();\n }\n } else {\n // For thread scope, launch for this specific thread\n if (!this.threadManager.isBrowserRunning(effectiveThreadId)) {\n await this.threadManager.getManagerForThread(effectiveThreadId);\n // Set status to ready so isBrowserRunning() returns true\n // (base class launch() does this, but we bypass it for thread scope)\n this.status = 'ready';\n }\n }\n }\n\n /**\n * Handle browser disconnection.\n * Overrides base class method.\n */\n override handleBrowserDisconnected(): void {\n // Call parent to handle status and notifications\n super.handleBrowserDisconnected();\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * Use this when an agent is using their own external CDP (e.g., browser-use cloud).\n * Connects Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n override async connectToExternalCdp(cdpUrl: string, threadId?: string): Promise<void> {\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n await this.threadManager.connectToExternalCdp(cdpUrl, effectiveThreadId);\n // Mark as ready\n this.status = 'ready';\n }\n\n // ---------------------------------------------------------------------------\n // Browser State (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async getActivePage(threadId?: string): Promise<Page | null> {\n return this.threadManager.getActivePageForThread(threadId ?? this.getCurrentThread());\n }\n\n protected override getBrowserStateForThread(threadId?: string): BrowserState | null {\n const context = this.threadManager.getContextForThread(threadId ?? this.getCurrentThread());\n if (!context) {\n return null;\n }\n\n const pages = context.pages();\n // Active page is the last one (most recently opened), consistent with resolveActivePage\n const activeIndex = pages.length > 0 ? pages.length - 1 : 0;\n const tabs: BrowserTabState[] = pages.map((page, index) => ({\n url: page.url(),\n title: '', // Would need async call to get title\n isActive: index === activeIndex,\n }));\n\n return {\n tabs,\n activeTabIndex: activeIndex,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Screencast Support\n // ---------------------------------------------------------------------------\n\n override async startScreencast(options?: ScreencastOptions): Promise<ScreencastStream> {\n const threadId = options?.threadId ?? this.getCurrentThread();\n\n // Create CDP session provider that creates FRESH sessions on each call\n // This is critical for tab switching - when reconnecting, we need a CDP session\n // attached to the CURRENT page, not the original page from launch\n const provider: CdpSessionProvider = {\n getCdpSession: async () => {\n const cdpSession = await this.threadManager.createFreshCdpSession(threadId);\n if (!cdpSession) {\n throw new Error('No browser context available for screencast');\n }\n\n // Return wrapper that implements CdpSessionLike\n return {\n send: async (method: string, params?: Record<string, unknown>) => {\n return cdpSession.send(method as any, params);\n },\n on: (event: string, handler: (params: unknown) => void) => {\n cdpSession.on(event as any, handler);\n },\n off: (event: string, handler: (params: unknown) => void) => {\n cdpSession.off(event as any, handler);\n },\n };\n },\n isBrowserRunning: () => this.isBrowserRunning(threadId),\n };\n\n // Create and start screencast stream\n const stream = new ScreencastStreamImpl(provider, {\n format: options?.format ?? 'jpeg',\n quality: options?.quality ?? 80,\n maxWidth: options?.maxWidth ?? 1280,\n maxHeight: options?.maxHeight ?? 720,\n everyNthFrame: options?.everyNthFrame ?? 1,\n });\n\n // Set up tab change detection - reconnect screencast when tabs change\n const context = this.threadManager.getContextForThread(threadId);\n if (context) {\n // Track all listeners for cleanup\n const pageListeners = new Map<Page, (frame: { url: () => string; parentFrame: () => unknown }) => void>();\n\n // New tab opened - reconnect screencast\n const onNewPage = () => {\n setTimeout(() => {\n if (stream.isActive()) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n };\n\n // Handler for new pages that sets up listeners\n const onPageCreated = (page: Page) => {\n setupPageListeners(page);\n };\n\n // Set up page close listener for each page\n const setupPageListeners = (page: Page) => {\n page.once('close', () => {\n // Clean up this page's listener\n pageListeners.delete(page);\n setTimeout(() => {\n if (stream.isActive() && context.pages().length > 0) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n });\n\n // Navigation listener for URL updates\n const onFrameNavigated = (frame: { url: () => string; parentFrame: () => unknown }) => {\n if (!frame.parentFrame()) {\n stream.emitUrl(frame.url());\n }\n };\n page.on('framenavigated', onFrameNavigated);\n pageListeners.set(page, onFrameNavigated);\n };\n\n // Set up listeners\n context.on('page', onNewPage);\n context.on('page', onPageCreated);\n\n // Set up for existing pages\n for (const page of context.pages()) {\n setupPageListeners(page);\n }\n\n // Clean up all listeners on stream stop\n stream.once('stop', () => {\n context.off('page', onNewPage);\n context.off('page', onPageCreated);\n // Remove framenavigated listeners from all pages\n for (const [page, listener] of pageListeners) {\n page.off('framenavigated', listener);\n }\n pageListeners.clear();\n });\n }\n\n await stream.start();\n return stream;\n }\n\n // ---------------------------------------------------------------------------\n // Input Injection\n // ---------------------------------------------------------------------------\n\n override async injectMouseEvent(params: MouseEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for mouse injection');\n }\n\n await cdpSession.send('Input.dispatchMouseEvent', params);\n }\n\n override async injectKeyboardEvent(params: KeyboardEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for keyboard injection');\n }\n\n await cdpSession.send('Input.dispatchKeyEvent', params);\n }\n\n // ---------------------------------------------------------------------------\n // Tools (CLI agents don't use SDK tools - they use workspace commands)\n // ---------------------------------------------------------------------------\n\n getTools(): Record<string, Tool> {\n // CLI agents use workspace_execute_command with CLI skills\n // No SDK tools needed\n return {};\n }\n}\n","/**\n * BrowserViewerThreadManager - Thread scope management for BrowserViewer\n *\n * Manages thread-scoped browser sessions using Playwright to launch\n * separate Chrome instances per thread.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { ThreadManager, DEFAULT_THREAD_ID } from '@mastra/core/browser';\nimport type { ThreadSession, ThreadManagerConfig } from '@mastra/core/browser';\nimport { chromium } from 'playwright-core';\nimport type { Browser, BrowserContext, BrowserServer, CDPSession, Page } from 'playwright-core';\nimport type { BrowserViewerConfig } from './types';\n\n/**\n * Extended session info for BrowserViewer.\n */\ninterface BrowserViewerSession extends ThreadSession {\n /**\n * Playwright browser server (owns the Chrome process).\n * Null for external CDP connections where we don't own the browser process.\n */\n browserServer: BrowserServer | null;\n /** Playwright browser instance (connected to server) */\n browser: Browser;\n /** Browser context */\n context: BrowserContext;\n /** CDP session for the active page */\n cdpSession: CDPSession | null;\n /** CDP WebSocket URL (null if discovery failed) */\n cdpUrl: string | null;\n}\n\n/**\n * Configuration for BrowserViewerThreadManager.\n */\nexport interface BrowserViewerThreadManagerConfig extends ThreadManagerConfig {\n /** Browser configuration */\n browserConfig: BrowserViewerConfig;\n /** Callback when a browser is created for a thread */\n onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n /** Callback when a browser is closed for a thread */\n onBrowserClosed?: (threadId: string) => void;\n}\n\n/**\n * Thread manager implementation for BrowserViewer.\n *\n * Supports two scope modes:\n * - 'shared': All threads share one Chrome instance\n * - 'thread': Each thread gets a dedicated Chrome instance\n */\nexport class BrowserViewerThreadManager extends ThreadManager<Browser> {\n private readonly browserConfig: BrowserViewerConfig;\n private readonly onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n private readonly onBrowserClosed?: (threadId: string) => void;\n\n /** Map of thread ID to session info (for 'thread' scope) */\n private readonly threadSessions = new Map<string, BrowserViewerSession>();\n\n /** Shared session info (for 'shared' scope) */\n private sharedSession: BrowserViewerSession | null = null;\n\n /** Cached CDP sessions for input injection, keyed by threadId */\n private inputCdpSessions = new Map<string, { session: CDPSession; pageUrl: string }>();\n\n constructor(config: BrowserViewerThreadManagerConfig) {\n super(config);\n this.browserConfig = config.browserConfig;\n this.onBrowserCreated = config.onBrowserCreated;\n this.onBrowserClosed = config.onBrowserClosed;\n }\n\n /**\n * Check if a thread should use the shared session slot.\n * In shared scope, all threads use the shared session.\n * In thread scope, DEFAULT_THREAD_ID also uses the shared session.\n */\n private usesSharedSlot(threadId: string): boolean {\n return this.scope === 'shared' || threadId === DEFAULT_THREAD_ID;\n }\n\n /**\n * Get the viewer session for a thread, using consistent routing.\n * Handles both shared and thread-scoped sessions.\n */\n private getViewerSession(threadId: string): BrowserViewerSession | null {\n if (this.usesSharedSlot(threadId)) {\n return this.sharedSession;\n }\n return this.threadSessions.get(threadId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Session Storage & Cleanup Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Store a session in the appropriate slot based on scope.\n * Consolidates session storage logic used by createSession, createSharedSession,\n * createSharedSessionFromCdp, and connectToExternalCdp.\n */\n private storeSession(session: BrowserViewerSession, threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = session;\n this.sessions.set(DEFAULT_THREAD_ID, session);\n this.setSharedManager(session.browser);\n } else {\n this.threadSessions.set(threadId, session);\n this.sessions.set(threadId, session);\n this.threadManagers.set(threadId, session.browser);\n }\n }\n\n /**\n * Clear a session from the appropriate slot based on scope.\n * Must be called BEFORE async cleanup operations to prevent double callbacks\n * from disconnect handlers.\n */\n private clearSessionState(threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = null;\n this.clearSharedManager();\n this.sessions.delete(DEFAULT_THREAD_ID);\n } else {\n this.threadSessions.delete(threadId);\n this.threadManagers.delete(threadId);\n this.sessions.delete(threadId);\n }\n }\n\n /**\n * Clean up a session's resources (CDP session, browser, server).\n * Consolidates cleanup logic used by closeThreadBrowser, closeSharedBrowser,\n * and doDestroySession.\n *\n * @param session - The session to clean up\n * @param threadId - The thread ID (for onBrowserClosed callback)\n */\n private async cleanupSession(session: BrowserViewerSession, threadId: string): Promise<void> {\n // Clear state BEFORE async operations to prevent double callback from disconnect handler\n this.clearSessionState(threadId);\n // Clear cached input CDP session\n this.inputCdpSessions.delete(threadId);\n\n // Detach CDP session\n if (session.cdpSession) {\n try {\n await session.cdpSession.detach();\n } catch {\n // Ignore - session may already be detached\n }\n }\n\n // Close browser connection\n try {\n await session.browser.close();\n } catch {\n // Ignore - browser may already be closed\n }\n\n // Close browser server (kills the Chrome process) - only if we own it\n if (session.browserServer) {\n try {\n await session.browserServer.close();\n } catch {\n // Ignore - server may already be closed\n }\n }\n\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Launch a new browser instance and return the components.\n * Consolidates the launch logic shared by createSession and createSharedSession.\n *\n * @param threadId - Thread ID for logging and disconnect handler\n */\n private async launchBrowser(threadId: string): Promise<{\n browserServer: BrowserServer;\n browser: Browser;\n context: BrowserContext;\n cdpSession: CDPSession | null;\n cdpUrl: string | null;\n }> {\n const cdpPort = this.browserConfig.cdpPort ?? 0;\n\n this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);\n\n const launchOptions: Parameters<typeof chromium.launchServer>[0] = {\n headless: this.browserConfig.headless ?? false,\n args: [`--remote-debugging-port=${cdpPort}`, '--no-first-run', '--no-default-browser-check'],\n };\n\n if (this.browserConfig.executablePath) {\n launchOptions.executablePath = this.browserConfig.executablePath;\n }\n\n // Track partially initialized resources for cleanup on failure\n let browserServer: BrowserServer | null = null;\n let browser: Browser | null = null;\n\n try {\n // Launch server - this starts Chrome\n browserServer = await chromium.launchServer(launchOptions);\n\n // Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file\n const cdpUrl = this.discoverCdpUrl(browserServer);\n\n // Connect to the browser via Playwright for screencast/session management\n browser = await chromium.connect(browserServer.wsEndpoint());\n\n // Create context and initial page\n const context = await browser.newContext({\n viewport: this.browserConfig.viewport ?? { width: 1280, height: 720 },\n });\n\n await context.newPage();\n\n // Set up CDP session for active page (used for screencast/input injection)\n const pages = context.pages();\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - multiple events can indicate browser closure:\n // - browserServer.on('close'): fires when Chrome process exits\n // - browser.on('disconnected'): fires when Playwright connection is lost\n // - CDP Target.targetDestroyed: fires when any target (page/context) is destroyed\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(threadId);\n };\n\n // Listen for browser server close (fires when Chrome process exits)\n browserServer.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n // Use browser-level CDP session to watch for ALL target destruction\n // Page-level CDP session only sees events for that specific page, but CLI creates its own pages\n // Browser-level session sees all targets across all contexts\n try {\n const browserCdpSession = await browser.newBrowserCDPSession();\n // Enable target discovery to get notified of all targets\n await browserCdpSession.send('Target.setDiscoverTargets', { discover: true });\n\n browserCdpSession.on('Target.targetDestroyed', async () => {\n // When a target is destroyed, check if any page targets remain\n // browser.isConnected() stays true because browserServer keeps Chrome alive,\n // so we need to check for actual page targets instead\n try {\n const { targetInfos } = (await browserCdpSession.send('Target.getTargets')) as {\n targetInfos: Array<{ type: string; url: string }>;\n };\n // Filter to actual page targets (not background pages, service workers, etc.)\n const pageTargets = targetInfos.filter(\n t => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'),\n );\n if (pageTargets.length === 0) {\n handleDisconnect();\n }\n } catch {\n // CDP session dead, browser definitely closed\n handleDisconnect();\n }\n });\n\n // Also listen for detached event (fires when CDP connection is lost)\n browserCdpSession.on('Inspector.detached', handleDisconnect);\n } catch {\n // Non-fatal: target watching is a reliability enhancement, not required\n this.logger?.debug?.('Failed to set up browser-level CDP target watching');\n }\n\n return { browserServer, browser, context, cdpSession, cdpUrl };\n } catch (error) {\n // Clean up partially initialized resources\n this.logger?.warn?.(`Failed to launch browser for thread ${threadId}: ${error}`);\n await browser?.close().catch(() => {});\n await browserServer?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Get CDP URL for a specific thread.\n */\n getCdpUrlForThread(threadId?: string): string | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.cdpUrl ?? null;\n }\n\n /**\n * Get the active page for a thread.\n */\n async getActivePageForThread(threadId?: string): Promise<Page | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n return this.resolveActivePage(session.context);\n }\n\n /**\n * Resolve the active page from a browser context.\n * Uses last page (most recently opened) with fallback to first page.\n */\n private resolveActivePage(context: BrowserContext): Page | null {\n const pages = context.pages();\n return pages[pages.length - 1] ?? pages[0] ?? null;\n }\n\n /**\n * Get or create a CDP session for the active page in a thread.\n *\n * CDP sessions are page-scoped, so we create a fresh one for the currently active page\n * rather than caching one that may point to a closed or inactive page.\n */\n async getCdpSessionForThread(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n // Check if browser is still connected - if not, trigger cleanup\n if (session.browser && !session.browser.isConnected()) {\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n\n if (!activePage) {\n return null;\n }\n\n // Check if we have a cached CDP session for the current active page\n const cached = this.inputCdpSessions.get(effectiveThreadId);\n const currentUrl = activePage.url();\n if (cached && cached.pageUrl === currentUrl) {\n // Reuse cached session if same page\n return cached.session;\n }\n\n // Create a new CDP session for the active page\n try {\n const cdpSession = await session.context.newCDPSession(activePage);\n // Cache it for future input events\n this.inputCdpSessions.set(effectiveThreadId, { session: cdpSession, pageUrl: currentUrl });\n return cdpSession;\n } catch {\n // Page may have been closed between getting pages and creating session\n // This often indicates browser was closed - trigger cleanup\n this.inputCdpSessions.delete(effectiveThreadId);\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n }\n\n /**\n * Get the browser context for a thread.\n */\n getContextForThread(threadId?: string): BrowserContext | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.context ?? null;\n }\n\n /**\n * Create a fresh CDP session for the active page (not cached).\n * Used by screencast which needs fresh sessions on tab switches.\n */\n async createFreshCdpSession(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n if (!activePage) {\n return null;\n }\n\n try {\n return await session.context.newCDPSession(activePage);\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session for a thread.\n */\n protected async createSession(threadId: string): Promise<BrowserViewerSession> {\n const savedState = this.getSavedBrowserState(threadId);\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(threadId);\n\n const session: BrowserViewerSession = {\n threadId,\n createdAt: Date.now(),\n browserState: savedState,\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, threadId);\n\n this.logger?.debug?.(`Chrome launched for thread ${threadId}, CDP URL: ${cdpUrl}`);\n\n // Notify callback\n this.onBrowserCreated?.(browser, threadId, cdpUrl);\n\n return session;\n }\n\n /**\n * Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file.\n *\n * Playwright's BrowserServer exposes _userDataDirForTest which points to Chrome's\n * user data directory. Chrome writes a DevToolsActivePort file there containing:\n * Line 1: The debugging port number\n * Line 2: The browser WebSocket path (e.g., /devtools/browser/<guid>)\n *\n * This gives us the real CDP URL that external tools like agent-browser can connect to.\n * Returns null if discovery fails - callers should handle this case.\n */\n private discoverCdpUrl(browserServer: BrowserServer): string | null {\n // Access Playwright's internal user data directory\n const userDataDir = (browserServer as BrowserServer & { _userDataDirForTest?: string })._userDataDirForTest;\n\n if (!userDataDir) {\n this.logger?.warn?.('Could not access browser user data directory');\n return null;\n }\n\n const portFilePath = join(userDataDir, 'DevToolsActivePort');\n\n // Chrome may still be writing the file during startup - retry with a short deadline\n const deadline = Date.now() + 1500;\n while (!existsSync(portFilePath) && Date.now() < deadline) {\n // Use Atomics.wait for a non-blocking ~50ms sleep\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n\n if (!existsSync(portFilePath)) {\n this.logger?.warn?.('DevToolsActivePort file not found');\n return null;\n }\n\n try {\n const content = readFileSync(portFilePath, 'utf-8').trim().split('\\n');\n const port = content[0];\n const browserPath = content[1];\n\n if (!port || !browserPath) {\n this.logger?.warn?.('Invalid DevToolsActivePort content');\n return null;\n }\n\n const cdpUrl = `ws://127.0.0.1:${port}${browserPath}`;\n this.logger?.debug?.(`Discovered CDP URL from DevToolsActivePort: ${cdpUrl}`);\n return cdpUrl;\n } catch (error) {\n this.logger?.warn?.('Failed to read DevToolsActivePort file:', error);\n return null;\n }\n }\n\n /**\n * Create a shared session by connecting to an existing browser via CDP URL.\n * Used when BrowserViewer is configured with a cdpUrl to connect to an external browser.\n */\n async createSharedSessionFromCdp(cdpUrl: string): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n await this.connectToCdp(cdpUrl, DEFAULT_THREAD_ID);\n }\n\n /**\n * Create a shared session (for 'shared' scope).\n */\n async createSharedSession(): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(DEFAULT_THREAD_ID);\n\n const session: BrowserViewerSession = {\n threadId: DEFAULT_THREAD_ID,\n createdAt: Date.now(),\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, DEFAULT_THREAD_ID);\n\n this.logger?.debug?.(`Shared Chrome launched, CDP URL: ${cdpUrl}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, DEFAULT_THREAD_ID, cdpUrl);\n this.onSessionCreated?.(session);\n }\n\n /**\n * Handle browser disconnection for a thread.\n */\n private handleBrowserDisconnected(threadId: string): void {\n this.logger?.debug?.(`Browser disconnected for thread ${threadId}`);\n\n // Guard against already-closed session (browser.close() triggers 'disconnected')\n if (!this.getViewerSession(threadId)) return;\n\n // Use consolidated helper for state cleanup\n this.clearSessionState(threadId);\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * This is used when an agent is using their own external CDP (e.g., browser-use cloud).\n * We connect Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n async connectToExternalCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n // Close any existing session for this thread to avoid leaking browser processes\n if (this.getViewerSession(threadId)) {\n if (this.usesSharedSlot(threadId)) {\n await this.closeSharedBrowser();\n } else {\n await this.closeThreadBrowser(threadId);\n }\n }\n\n return this.connectToCdp(cdpUrl, threadId);\n }\n\n /**\n * Connect to a browser via CDP URL and create a session.\n * Shared implementation for createSharedSessionFromCdp and connectToExternalCdp.\n */\n private async connectToCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n const effectiveThreadId = this.usesSharedSlot(threadId) ? DEFAULT_THREAD_ID : threadId;\n this.logger?.debug?.(`Connecting to CDP for thread ${effectiveThreadId}: ${cdpUrl}`);\n\n let browser: Browser | null = null;\n\n try {\n browser = await chromium.connectOverCDP(cdpUrl);\n\n // Get or create context\n const contexts = browser.contexts();\n const context = contexts[0] ?? (await browser.newContext());\n\n // Get or create page\n let pages = context.pages();\n if (pages.length === 0) {\n // Wait briefly for external browser to create a page, or create one\n await new Promise(resolve => setTimeout(resolve, 500));\n pages = context.pages();\n if (pages.length === 0) {\n await context.newPage();\n pages = context.pages();\n }\n }\n\n // Set up CDP session for active page\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - use effectiveThreadId for consistent lifecycle callbacks\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(effectiveThreadId);\n };\n\n // Listen for context close (fires when browser window is closed manually)\n context.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n const session: BrowserViewerSession = {\n threadId: effectiveThreadId,\n createdAt: Date.now(),\n browserServer: null, // We don't own the server for external CDP connections\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n this.storeSession(session, threadId);\n this.logger?.debug?.(`Connected to CDP for thread ${effectiveThreadId}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, effectiveThreadId, cdpUrl);\n this.onSessionCreated?.(session);\n\n return session;\n } catch (error) {\n this.logger?.warn?.(`Failed to connect to CDP: ${error}`);\n await browser?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Close a specific thread's browser.\n */\n async closeThreadBrowser(threadId: string): Promise<void> {\n const session = this.threadSessions.get(threadId);\n if (!session) {\n return;\n }\n await this.cleanupSession(session, threadId);\n }\n\n /**\n * Close the shared browser.\n */\n async closeSharedBrowser(): Promise<void> {\n if (!this.sharedSession) {\n return;\n }\n await this.cleanupSession(this.sharedSession, DEFAULT_THREAD_ID);\n }\n\n /**\n * Close all browsers.\n */\n async closeAll(): Promise<void> {\n // Close all thread browsers\n const threadIds = Array.from(this.threadSessions.keys());\n await Promise.all(threadIds.map(id => this.closeThreadBrowser(id)));\n\n // Close shared browser\n await this.closeSharedBrowser();\n }\n\n /**\n * Get the manager for a session.\n * Required by base class.\n */\n protected getManagerForSession(session: ThreadSession): Browser {\n const viewerSession = session as BrowserViewerSession;\n return viewerSession.browser;\n }\n\n /**\n * Get the shared manager.\n * Required by base class.\n */\n protected getSharedManager(): Browser {\n if (!this.sharedSession) {\n throw new Error('Shared browser not launched. Call createSharedSession() first.');\n }\n return this.sharedSession.browser;\n }\n\n /**\n * Destroy a session and clean up resources.\n * Required by base class.\n */\n protected async doDestroySession(session: ThreadSession): Promise<void> {\n const viewerSession = this.getViewerSession(session.threadId);\n if (!viewerSession) {\n return;\n }\n await this.cleanupSession(viewerSession, session.threadId);\n }\n\n /**\n * Check if browser is running for a thread.\n */\n isBrowserRunning(threadId?: string): boolean {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId) !== null;\n }\n}\n"],"mappings":"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","names":["import_browser"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser-viewer.ts","../src/thread-manager.ts"],"sourcesContent":["/**\n * @mastra/browser-viewer\n *\n * Browser viewer for Mastra workspaces with CLI provider support.\n * Launches Chrome via Playwright and exposes CDP URL for CLI tools.\n */\n\nexport { BrowserViewer } from './browser-viewer';\nexport { BrowserViewerThreadManager } from './thread-manager';\nexport type { BrowserViewerConfig, CLIProvider } from './types';\nexport type { BrowserViewerThreadManagerConfig } from './thread-manager';\n","/**\n * BrowserViewer - Playwright-managed Chrome for CLI providers\n *\n * Launches Chrome via Playwright and exposes the CDP URL for CLI tools\n * (agent-browser, browser-use, browse-cli) to connect as secondary clients.\n *\n * This gives us:\n * - Direct page-level CDP sessions (fixes screencast sessionId issues)\n * - Full browser lifecycle control\n * - Predictable CDP URL for CLI injection\n * - Thread-scoped browser isolation\n */\n\nimport { MastraBrowser, ScreencastStreamImpl } from '@mastra/core/browser';\nimport type {\n BrowserState,\n BrowserTabState,\n ScreencastOptions,\n ScreencastStream,\n CdpSessionProvider,\n MouseEventParams,\n KeyboardEventParams,\n} from '@mastra/core/browser';\nimport type { Tool } from '@mastra/core/tools';\nimport type { Page } from 'playwright-core';\nimport { BrowserViewerThreadManager } from './thread-manager';\nimport type { BrowserViewerConfig, CLIProvider } from './types';\n\n/**\n * BrowserViewer - CLI provider with Playwright-managed Chrome\n *\n * Use this with Workspace to enable browser automation via CLI tools.\n * The agent uses skills + workspace_execute_command to drive the CLI,\n * while Mastra handles screencast, input injection, and lifecycle.\n *\n * @example\n * ```ts\n * import { Workspace } from '@mastra/core';\n * import { BrowserViewer } from '@mastra/browser-viewer';\n *\n * const workspace = new Workspace({\n * browser: new BrowserViewer({\n * cli: 'agent-browser',\n * headless: false,\n * }),\n * });\n * ```\n */\nexport class BrowserViewer extends MastraBrowser {\n override readonly id: string;\n override readonly name = 'BrowserViewer';\n override readonly provider = 'browser-viewer';\n readonly providerType = 'cli' as const;\n\n /** Which CLI the agent uses */\n readonly cli: CLIProvider;\n\n /** Viewer-specific config (stored for reference) */\n readonly viewerConfig: BrowserViewerConfig;\n\n /** Thread manager for browser sessions */\n declare protected threadManager: BrowserViewerThreadManager;\n\n constructor(config: BrowserViewerConfig) {\n // Default to 'thread' scope (each thread gets its own Chrome)\n // Use 'shared' if connecting to an existing browser\n const effectiveScope = config.cdpUrl ? (config.scope ?? 'shared') : (config.scope ?? 'thread');\n\n // Build base config (exclude CLI-specific options)\n // Use type assertion because BrowserConfig is a discriminated union\n const { cli: _cli, cdpPort: _cdpPort, ...baseConfig } = config;\n\n super({\n ...baseConfig,\n scope: effectiveScope,\n } as any);\n\n this.id = `browser-viewer-${Date.now()}`;\n this.cli = config.cli;\n this.viewerConfig = config;\n\n // Initialize thread manager\n this.threadManager = new BrowserViewerThreadManager({\n scope: effectiveScope,\n browserConfig: { ...config, headless: this.headless },\n logger: this.logger,\n onSessionCreated: session => {\n // Notify listeners so screencast can start for this thread\n this.notifyBrowserReady(session.threadId);\n },\n onBrowserCreated: (_browser, threadId, _cdpUrl) => {\n this.logger?.debug?.(`Browser created for thread ${threadId}`);\n },\n onBrowserClosed: threadId => {\n this.logger?.debug?.(`Browser closed for thread ${threadId}`);\n // Notify base class callbacks so ViewerRegistry gets notified\n this.notifyBrowserClosed(threadId);\n },\n });\n }\n\n // ---------------------------------------------------------------------------\n // CDP URL Access\n // ---------------------------------------------------------------------------\n\n /**\n * Get the CDP WebSocket URL for CLI tools to connect.\n * For thread scope, returns the CDP URL for the specified thread.\n * For shared scope, returns the single shared CDP URL.\n *\n * @param threadId - Thread identifier (optional, uses current thread if not specified)\n * @returns CDP URL or null if browser not running for that thread\n */\n override getCdpUrl(threadId?: string): string | null {\n return this.threadManager.getCdpUrlForThread(threadId ?? this.getCurrentThread());\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async doLaunch(): Promise<void> {\n const scope = this.threadManager.getScope();\n const cdpUrl = this.config.cdpUrl;\n\n if (cdpUrl) {\n // Connect mode: connect to existing browser (always shared)\n const url = typeof cdpUrl === 'function' ? await cdpUrl() : cdpUrl;\n await this.connectToExisting(url);\n } else if (scope === 'shared') {\n // Shared mode: launch single browser\n await this.threadManager.createSharedSession();\n }\n // For thread scope, browsers are launched lazily per thread via ensureReady()\n }\n\n protected override async doClose(): Promise<void> {\n await this.threadManager.closeAll();\n }\n\n /**\n * Connect to an existing browser via CDP URL.\n */\n private async connectToExisting(cdpUrl: string): Promise<void> {\n this.logger?.debug?.(`Connecting to existing browser at ${cdpUrl}`);\n\n // Create a shared session from the external CDP connection\n await this.threadManager.createSharedSessionFromCdp(cdpUrl);\n\n this.logger?.debug?.('Connected to existing browser');\n }\n\n /**\n * Ensure browser is ready for the current thread.\n * For thread scope, creates a new browser if needed.\n */\n override async ensureReady(): Promise<void> {\n const scope = this.threadManager.getScope();\n const threadId = this.getCurrentThread();\n\n // For thread scope, create browser for this thread if needed\n if (scope === 'thread' && !this.threadManager.isBrowserRunning(threadId)) {\n await this.threadManager.getManagerForThread(threadId);\n }\n\n await super.ensureReady();\n }\n\n /**\n * Check if browser is running (for current thread in thread scope).\n */\n override isBrowserRunning(threadId?: string): boolean {\n return this.threadManager.isBrowserRunning(threadId ?? this.getCurrentThread());\n }\n\n /**\n * Launch browser, optionally for a specific thread.\n * For thread scope, creates a browser for that thread.\n * For shared scope, launches the single shared browser.\n */\n override async launch(threadId?: string): Promise<void> {\n const scope = this.threadManager.getScope();\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n\n if (scope === 'shared') {\n // For shared scope, use base class launch (handles racing, status, etc.)\n if (!this.threadManager.isBrowserRunning()) {\n await super.launch();\n }\n } else {\n // For thread scope, launch for this specific thread\n if (!this.threadManager.isBrowserRunning(effectiveThreadId)) {\n await this.threadManager.getManagerForThread(effectiveThreadId);\n // Set status to ready so isBrowserRunning() returns true\n // (base class launch() does this, but we bypass it for thread scope)\n this.status = 'ready';\n }\n }\n }\n\n /**\n * Handle browser disconnection.\n * Overrides base class method.\n */\n override handleBrowserDisconnected(): void {\n // Call parent to handle status and notifications\n super.handleBrowserDisconnected();\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * Use this when an agent is using their own external CDP (e.g., browser-use cloud).\n * Connects Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n override async connectToExternalCdp(cdpUrl: string, threadId?: string): Promise<void> {\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n await this.threadManager.connectToExternalCdp(cdpUrl, effectiveThreadId);\n // Mark as ready\n this.status = 'ready';\n }\n\n // ---------------------------------------------------------------------------\n // Browser State (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async getActivePage(threadId?: string): Promise<Page | null> {\n return this.threadManager.getActivePageForThread(threadId ?? this.getCurrentThread());\n }\n\n protected override getBrowserStateForThread(threadId?: string): BrowserState | null {\n const context = this.threadManager.getContextForThread(threadId ?? this.getCurrentThread());\n if (!context) {\n return null;\n }\n\n const pages = context.pages();\n // Active page is the last one (most recently opened), consistent with resolveActivePage\n const activeIndex = pages.length > 0 ? pages.length - 1 : 0;\n const tabs: BrowserTabState[] = pages.map((page, index) => ({\n url: page.url(),\n title: '', // Would need async call to get title\n isActive: index === activeIndex,\n }));\n\n return {\n tabs,\n activeTabIndex: activeIndex,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Screencast Support\n // ---------------------------------------------------------------------------\n\n override async startScreencast(options?: ScreencastOptions): Promise<ScreencastStream> {\n const threadId = options?.threadId ?? this.getCurrentThread();\n\n // Create CDP session provider that creates FRESH sessions on each call\n // This is critical for tab switching - when reconnecting, we need a CDP session\n // attached to the CURRENT page, not the original page from launch\n const provider: CdpSessionProvider = {\n getCdpSession: async () => {\n const cdpSession = await this.threadManager.createFreshCdpSession(threadId);\n if (!cdpSession) {\n throw new Error('No browser context available for screencast');\n }\n\n // Return wrapper that implements CdpSessionLike\n return {\n send: async (method: string, params?: Record<string, unknown>) => {\n return cdpSession.send(method as any, params);\n },\n on: (event: string, handler: (params: unknown) => void) => {\n cdpSession.on(event as any, handler);\n },\n off: (event: string, handler: (params: unknown) => void) => {\n cdpSession.off(event as any, handler);\n },\n };\n },\n isBrowserRunning: () => this.isBrowserRunning(threadId),\n };\n\n // Create and start screencast stream\n const stream = new ScreencastStreamImpl(provider, {\n format: options?.format ?? 'jpeg',\n quality: options?.quality ?? 80,\n maxWidth: options?.maxWidth ?? 1280,\n maxHeight: options?.maxHeight ?? 720,\n everyNthFrame: options?.everyNthFrame ?? 1,\n });\n\n // Set up tab change detection - reconnect screencast when tabs change\n const context = this.threadManager.getContextForThread(threadId);\n if (context) {\n // Track all listeners for cleanup\n const pageListeners = new Map<Page, (frame: { url: () => string; parentFrame: () => unknown }) => void>();\n\n // New tab opened - reconnect screencast\n const onNewPage = () => {\n setTimeout(() => {\n if (stream.isActive()) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n };\n\n // Handler for new pages that sets up listeners\n const onPageCreated = (page: Page) => {\n setupPageListeners(page);\n };\n\n // Set up page close listener for each page\n const setupPageListeners = (page: Page) => {\n page.once('close', () => {\n // Clean up this page's listener\n pageListeners.delete(page);\n setTimeout(() => {\n if (stream.isActive() && context.pages().length > 0) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n });\n\n // Navigation listener for URL updates\n const onFrameNavigated = (frame: { url: () => string; parentFrame: () => unknown }) => {\n if (!frame.parentFrame()) {\n stream.emitUrl(frame.url());\n }\n };\n page.on('framenavigated', onFrameNavigated);\n pageListeners.set(page, onFrameNavigated);\n };\n\n // Set up listeners\n context.on('page', onNewPage);\n context.on('page', onPageCreated);\n\n // Set up for existing pages\n for (const page of context.pages()) {\n setupPageListeners(page);\n }\n\n // Clean up all listeners on stream stop\n stream.once('stop', () => {\n context.off('page', onNewPage);\n context.off('page', onPageCreated);\n // Remove framenavigated listeners from all pages\n for (const [page, listener] of pageListeners) {\n page.off('framenavigated', listener);\n }\n pageListeners.clear();\n });\n }\n\n await stream.start();\n return stream;\n }\n\n // ---------------------------------------------------------------------------\n // Input Injection\n // ---------------------------------------------------------------------------\n\n override async injectMouseEvent(params: MouseEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for mouse injection');\n }\n\n await cdpSession.send('Input.dispatchMouseEvent', params);\n }\n\n override async injectKeyboardEvent(params: KeyboardEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for keyboard injection');\n }\n\n await cdpSession.send('Input.dispatchKeyEvent', params);\n }\n\n // ---------------------------------------------------------------------------\n // Tools (CLI agents don't use SDK tools - they use workspace commands)\n // ---------------------------------------------------------------------------\n\n getTools(): Record<string, Tool> {\n // CLI agents use workspace_execute_command with CLI skills\n // No SDK tools needed\n return {};\n }\n}\n","/**\n * BrowserViewerThreadManager - Thread scope management for BrowserViewer\n *\n * Manages thread-scoped browser sessions using Playwright to launch\n * separate Chrome instances per thread.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { ThreadManager, DEFAULT_THREAD_ID } from '@mastra/core/browser';\nimport type { ThreadSession, ThreadManagerConfig } from '@mastra/core/browser';\nimport { chromium } from 'playwright-core';\nimport type { Browser, BrowserContext, BrowserServer, CDPSession, Page } from 'playwright-core';\nimport type { BrowserViewerConfig } from './types';\n\n/**\n * Extended session info for BrowserViewer.\n */\ninterface BrowserViewerSession extends ThreadSession {\n /**\n * Playwright browser server (owns the Chrome process).\n * Null for external CDP connections where we don't own the browser process.\n */\n browserServer: BrowserServer | null;\n /** Playwright browser instance (connected to server) */\n browser: Browser;\n /** Browser context */\n context: BrowserContext;\n /** CDP session for the active page */\n cdpSession: CDPSession | null;\n /** CDP WebSocket URL (null if discovery failed) */\n cdpUrl: string | null;\n}\n\n/**\n * Configuration for BrowserViewerThreadManager.\n */\nexport interface BrowserViewerThreadManagerConfig extends ThreadManagerConfig {\n /** Browser configuration */\n browserConfig: BrowserViewerConfig;\n /** Callback when a browser is created for a thread */\n onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n /** Callback when a browser is closed for a thread */\n onBrowserClosed?: (threadId: string) => void;\n}\n\n/**\n * Thread manager implementation for BrowserViewer.\n *\n * Supports two scope modes:\n * - 'shared': All threads share one Chrome instance\n * - 'thread': Each thread gets a dedicated Chrome instance\n */\nexport class BrowserViewerThreadManager extends ThreadManager<Browser> {\n private readonly browserConfig: BrowserViewerConfig;\n private readonly onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n private readonly onBrowserClosed?: (threadId: string) => void;\n\n /** Map of thread ID to session info (for 'thread' scope) */\n private readonly threadSessions = new Map<string, BrowserViewerSession>();\n\n /** Shared session info (for 'shared' scope) */\n private sharedSession: BrowserViewerSession | null = null;\n\n /** Cached CDP sessions for input injection, keyed by threadId */\n private inputCdpSessions = new Map<string, { session: CDPSession; pageUrl: string }>();\n\n constructor(config: BrowserViewerThreadManagerConfig) {\n super(config);\n this.browserConfig = config.browserConfig;\n this.onBrowserCreated = config.onBrowserCreated;\n this.onBrowserClosed = config.onBrowserClosed;\n }\n\n /**\n * Check if a thread should use the shared session slot.\n * In shared scope, all threads use the shared session.\n * In thread scope, DEFAULT_THREAD_ID also uses the shared session.\n */\n private usesSharedSlot(threadId: string): boolean {\n return this.scope === 'shared' || threadId === DEFAULT_THREAD_ID;\n }\n\n /**\n * Get the viewer session for a thread, using consistent routing.\n * Handles both shared and thread-scoped sessions.\n */\n private getViewerSession(threadId: string): BrowserViewerSession | null {\n if (this.usesSharedSlot(threadId)) {\n return this.sharedSession;\n }\n return this.threadSessions.get(threadId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Session Storage & Cleanup Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Store a session in the appropriate slot based on scope.\n * Consolidates session storage logic used by createSession, createSharedSession,\n * createSharedSessionFromCdp, and connectToExternalCdp.\n */\n private storeSession(session: BrowserViewerSession, threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = session;\n this.sessions.set(DEFAULT_THREAD_ID, session);\n this.setSharedManager(session.browser);\n } else {\n this.threadSessions.set(threadId, session);\n this.sessions.set(threadId, session);\n this.threadManagers.set(threadId, session.browser);\n }\n }\n\n /**\n * Clear a session from the appropriate slot based on scope.\n * Must be called BEFORE async cleanup operations to prevent double callbacks\n * from disconnect handlers.\n */\n private clearSessionState(threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = null;\n this.clearSharedManager();\n this.sessions.delete(DEFAULT_THREAD_ID);\n } else {\n this.threadSessions.delete(threadId);\n this.threadManagers.delete(threadId);\n this.sessions.delete(threadId);\n }\n }\n\n /**\n * Clean up a session's resources (CDP session, browser, server).\n * Consolidates cleanup logic used by closeThreadBrowser, closeSharedBrowser,\n * and doDestroySession.\n *\n * @param session - The session to clean up\n * @param threadId - The thread ID (for onBrowserClosed callback)\n */\n private async cleanupSession(session: BrowserViewerSession, threadId: string): Promise<void> {\n // Clear state BEFORE async operations to prevent double callback from disconnect handler\n this.clearSessionState(threadId);\n // Clear cached input CDP session\n this.inputCdpSessions.delete(threadId);\n\n // Detach CDP session\n if (session.cdpSession) {\n try {\n await session.cdpSession.detach();\n } catch {\n // Ignore - session may already be detached\n }\n }\n\n // Close browser connection\n try {\n await session.browser.close();\n } catch {\n // Ignore - browser may already be closed\n }\n\n // Close browser server (kills the Chrome process) - only if we own it\n if (session.browserServer) {\n try {\n await session.browserServer.close();\n } catch {\n // Ignore - server may already be closed\n }\n }\n\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Launch a new browser instance and return the components.\n * Consolidates the launch logic shared by createSession and createSharedSession.\n *\n * @param threadId - Thread ID for logging and disconnect handler\n */\n private async launchBrowser(threadId: string): Promise<{\n browserServer: BrowserServer;\n browser: Browser;\n context: BrowserContext;\n cdpSession: CDPSession | null;\n cdpUrl: string | null;\n }> {\n const cdpPort = this.browserConfig.cdpPort ?? 0;\n\n this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);\n\n const launchOptions: Parameters<typeof chromium.launchServer>[0] = {\n headless: this.browserConfig.headless,\n args: [`--remote-debugging-port=${cdpPort}`, '--no-first-run', '--no-default-browser-check'],\n };\n\n if (this.browserConfig.executablePath) {\n launchOptions.executablePath = this.browserConfig.executablePath;\n }\n\n // Track partially initialized resources for cleanup on failure\n let browserServer: BrowserServer | null = null;\n let browser: Browser | null = null;\n\n try {\n // Launch server - this starts Chrome\n browserServer = await chromium.launchServer(launchOptions);\n\n // Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file\n const cdpUrl = this.discoverCdpUrl(browserServer);\n\n // Connect to the browser via Playwright for screencast/session management\n browser = await chromium.connect(browserServer.wsEndpoint());\n\n // Create context and initial page\n const context = await browser.newContext({\n viewport: this.browserConfig.viewport ?? { width: 1280, height: 720 },\n });\n\n await context.newPage();\n\n // Set up CDP session for active page (used for screencast/input injection)\n const pages = context.pages();\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - multiple events can indicate browser closure:\n // - browserServer.on('close'): fires when Chrome process exits\n // - browser.on('disconnected'): fires when Playwright connection is lost\n // - CDP Target.targetDestroyed: fires when any target (page/context) is destroyed\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(threadId);\n };\n\n // Listen for browser server close (fires when Chrome process exits)\n browserServer.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n // Use browser-level CDP session to watch for ALL target destruction\n // Page-level CDP session only sees events for that specific page, but CLI creates its own pages\n // Browser-level session sees all targets across all contexts\n try {\n const browserCdpSession = await browser.newBrowserCDPSession();\n // Enable target discovery to get notified of all targets\n await browserCdpSession.send('Target.setDiscoverTargets', { discover: true });\n\n browserCdpSession.on('Target.targetDestroyed', async () => {\n // When a target is destroyed, check if any page targets remain\n // browser.isConnected() stays true because browserServer keeps Chrome alive,\n // so we need to check for actual page targets instead\n try {\n const { targetInfos } = (await browserCdpSession.send('Target.getTargets')) as {\n targetInfos: Array<{ type: string; url: string }>;\n };\n // Filter to actual page targets (not background pages, service workers, etc.)\n const pageTargets = targetInfos.filter(\n t => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'),\n );\n if (pageTargets.length === 0) {\n handleDisconnect();\n }\n } catch {\n // CDP session dead, browser definitely closed\n handleDisconnect();\n }\n });\n\n // Also listen for detached event (fires when CDP connection is lost)\n browserCdpSession.on('Inspector.detached', handleDisconnect);\n } catch {\n // Non-fatal: target watching is a reliability enhancement, not required\n this.logger?.debug?.('Failed to set up browser-level CDP target watching');\n }\n\n return { browserServer, browser, context, cdpSession, cdpUrl };\n } catch (error) {\n // Clean up partially initialized resources\n this.logger?.warn?.(`Failed to launch browser for thread ${threadId}: ${error}`);\n await browser?.close().catch(() => {});\n await browserServer?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Get CDP URL for a specific thread.\n */\n getCdpUrlForThread(threadId?: string): string | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.cdpUrl ?? null;\n }\n\n /**\n * Get the active page for a thread.\n */\n async getActivePageForThread(threadId?: string): Promise<Page | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n return this.resolveActivePage(session.context);\n }\n\n /**\n * Resolve the active page from a browser context.\n * Uses last page (most recently opened) with fallback to first page.\n */\n private resolveActivePage(context: BrowserContext): Page | null {\n const pages = context.pages();\n return pages[pages.length - 1] ?? pages[0] ?? null;\n }\n\n /**\n * Get or create a CDP session for the active page in a thread.\n *\n * CDP sessions are page-scoped, so we create a fresh one for the currently active page\n * rather than caching one that may point to a closed or inactive page.\n */\n async getCdpSessionForThread(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n // Check if browser is still connected - if not, trigger cleanup\n if (session.browser && !session.browser.isConnected()) {\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n\n if (!activePage) {\n return null;\n }\n\n // Check if we have a cached CDP session for the current active page\n const cached = this.inputCdpSessions.get(effectiveThreadId);\n const currentUrl = activePage.url();\n if (cached && cached.pageUrl === currentUrl) {\n // Reuse cached session if same page\n return cached.session;\n }\n\n // Create a new CDP session for the active page\n try {\n const cdpSession = await session.context.newCDPSession(activePage);\n // Cache it for future input events\n this.inputCdpSessions.set(effectiveThreadId, { session: cdpSession, pageUrl: currentUrl });\n return cdpSession;\n } catch {\n // Page may have been closed between getting pages and creating session\n // This often indicates browser was closed - trigger cleanup\n this.inputCdpSessions.delete(effectiveThreadId);\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n }\n\n /**\n * Get the browser context for a thread.\n */\n getContextForThread(threadId?: string): BrowserContext | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.context ?? null;\n }\n\n /**\n * Create a fresh CDP session for the active page (not cached).\n * Used by screencast which needs fresh sessions on tab switches.\n */\n async createFreshCdpSession(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n if (!activePage) {\n return null;\n }\n\n try {\n return await session.context.newCDPSession(activePage);\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session for a thread.\n */\n protected async createSession(threadId: string): Promise<BrowserViewerSession> {\n const savedState = this.getSavedBrowserState(threadId);\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(threadId);\n\n const session: BrowserViewerSession = {\n threadId,\n createdAt: Date.now(),\n browserState: savedState,\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, threadId);\n\n this.logger?.debug?.(`Chrome launched for thread ${threadId}, CDP URL: ${cdpUrl}`);\n\n // Notify callback\n this.onBrowserCreated?.(browser, threadId, cdpUrl);\n\n return session;\n }\n\n /**\n * Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file.\n *\n * Playwright's BrowserServer exposes _userDataDirForTest which points to Chrome's\n * user data directory. Chrome writes a DevToolsActivePort file there containing:\n * Line 1: The debugging port number\n * Line 2: The browser WebSocket path (e.g., /devtools/browser/<guid>)\n *\n * This gives us the real CDP URL that external tools like agent-browser can connect to.\n * Returns null if discovery fails - callers should handle this case.\n */\n private discoverCdpUrl(browserServer: BrowserServer): string | null {\n // Access Playwright's internal user data directory\n const userDataDir = (browserServer as BrowserServer & { _userDataDirForTest?: string })._userDataDirForTest;\n\n if (!userDataDir) {\n this.logger?.warn?.('Could not access browser user data directory');\n return null;\n }\n\n const portFilePath = join(userDataDir, 'DevToolsActivePort');\n\n // Chrome may still be writing the file during startup - retry with a short deadline\n const deadline = Date.now() + 1500;\n while (!existsSync(portFilePath) && Date.now() < deadline) {\n // Use Atomics.wait for a non-blocking ~50ms sleep\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n\n if (!existsSync(portFilePath)) {\n this.logger?.warn?.('DevToolsActivePort file not found');\n return null;\n }\n\n try {\n const content = readFileSync(portFilePath, 'utf-8').trim().split('\\n');\n const port = content[0];\n const browserPath = content[1];\n\n if (!port || !browserPath) {\n this.logger?.warn?.('Invalid DevToolsActivePort content');\n return null;\n }\n\n const cdpUrl = `ws://127.0.0.1:${port}${browserPath}`;\n this.logger?.debug?.(`Discovered CDP URL from DevToolsActivePort: ${cdpUrl}`);\n return cdpUrl;\n } catch (error) {\n this.logger?.warn?.('Failed to read DevToolsActivePort file:', error);\n return null;\n }\n }\n\n /**\n * Create a shared session by connecting to an existing browser via CDP URL.\n * Used when BrowserViewer is configured with a cdpUrl to connect to an external browser.\n */\n async createSharedSessionFromCdp(cdpUrl: string): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n await this.connectToCdp(cdpUrl, DEFAULT_THREAD_ID);\n }\n\n /**\n * Create a shared session (for 'shared' scope).\n */\n async createSharedSession(): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(DEFAULT_THREAD_ID);\n\n const session: BrowserViewerSession = {\n threadId: DEFAULT_THREAD_ID,\n createdAt: Date.now(),\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, DEFAULT_THREAD_ID);\n\n this.logger?.debug?.(`Shared Chrome launched, CDP URL: ${cdpUrl}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, DEFAULT_THREAD_ID, cdpUrl);\n this.onSessionCreated?.(session);\n }\n\n /**\n * Handle browser disconnection for a thread.\n */\n private handleBrowserDisconnected(threadId: string): void {\n this.logger?.debug?.(`Browser disconnected for thread ${threadId}`);\n\n // Guard against already-closed session (browser.close() triggers 'disconnected')\n if (!this.getViewerSession(threadId)) return;\n\n // Use consolidated helper for state cleanup\n this.clearSessionState(threadId);\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * This is used when an agent is using their own external CDP (e.g., browser-use cloud).\n * We connect Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n async connectToExternalCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n // Close any existing session for this thread to avoid leaking browser processes\n if (this.getViewerSession(threadId)) {\n if (this.usesSharedSlot(threadId)) {\n await this.closeSharedBrowser();\n } else {\n await this.closeThreadBrowser(threadId);\n }\n }\n\n return this.connectToCdp(cdpUrl, threadId);\n }\n\n /**\n * Connect to a browser via CDP URL and create a session.\n * Shared implementation for createSharedSessionFromCdp and connectToExternalCdp.\n */\n private async connectToCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n const effectiveThreadId = this.usesSharedSlot(threadId) ? DEFAULT_THREAD_ID : threadId;\n this.logger?.debug?.(`Connecting to CDP for thread ${effectiveThreadId}: ${cdpUrl}`);\n\n let browser: Browser | null = null;\n\n try {\n browser = await chromium.connectOverCDP(cdpUrl);\n\n // Get or create context\n const contexts = browser.contexts();\n const context = contexts[0] ?? (await browser.newContext());\n\n // Get or create page\n let pages = context.pages();\n if (pages.length === 0) {\n // Wait briefly for external browser to create a page, or create one\n await new Promise(resolve => setTimeout(resolve, 500));\n pages = context.pages();\n if (pages.length === 0) {\n await context.newPage();\n pages = context.pages();\n }\n }\n\n // Set up CDP session for active page\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - use effectiveThreadId for consistent lifecycle callbacks\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(effectiveThreadId);\n };\n\n // Listen for context close (fires when browser window is closed manually)\n context.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n const session: BrowserViewerSession = {\n threadId: effectiveThreadId,\n createdAt: Date.now(),\n browserServer: null, // We don't own the server for external CDP connections\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n this.storeSession(session, threadId);\n this.logger?.debug?.(`Connected to CDP for thread ${effectiveThreadId}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, effectiveThreadId, cdpUrl);\n this.onSessionCreated?.(session);\n\n return session;\n } catch (error) {\n this.logger?.warn?.(`Failed to connect to CDP: ${error}`);\n await browser?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Close a specific thread's browser.\n */\n async closeThreadBrowser(threadId: string): Promise<void> {\n const session = this.threadSessions.get(threadId);\n if (!session) {\n return;\n }\n await this.cleanupSession(session, threadId);\n }\n\n /**\n * Close the shared browser.\n */\n async closeSharedBrowser(): Promise<void> {\n if (!this.sharedSession) {\n return;\n }\n await this.cleanupSession(this.sharedSession, DEFAULT_THREAD_ID);\n }\n\n /**\n * Close all browsers.\n */\n async closeAll(): Promise<void> {\n // Close all thread browsers\n const threadIds = Array.from(this.threadSessions.keys());\n await Promise.all(threadIds.map(id => this.closeThreadBrowser(id)));\n\n // Close shared browser\n await this.closeSharedBrowser();\n }\n\n /**\n * Get the manager for a session.\n * Required by base class.\n */\n protected getManagerForSession(session: ThreadSession): Browser {\n const viewerSession = session as BrowserViewerSession;\n return viewerSession.browser;\n }\n\n /**\n * Get the shared manager.\n * Required by base class.\n */\n protected getSharedManager(): Browser {\n if (!this.sharedSession) {\n throw new Error('Shared browser not launched. Call createSharedSession() first.');\n }\n return this.sharedSession.browser;\n }\n\n /**\n * Destroy a session and clean up resources.\n * Required by base class.\n */\n protected async doDestroySession(session: ThreadSession): Promise<void> {\n const viewerSession = this.getViewerSession(session.threadId);\n if (!viewerSession) {\n return;\n }\n await this.cleanupSession(viewerSession, session.threadId);\n }\n\n /**\n * Check if browser is running for a thread.\n */\n isBrowserRunning(threadId?: string): boolean {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId) !== null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACaA,IAAAA,kBAAoD;;;ACNpD,qBAAyC;AACzC,uBAAqB;AAErB,qBAAiD;AAEjD,6BAAyB;AA0ClB,IAAM,6BAAN,cAAyC,6BAAuB;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,iBAAiB,oBAAI,IAAkC;AAAA;AAAA,EAGhE,gBAA6C;AAAA;AAAA,EAG7C,mBAAmB,oBAAI,IAAsD;AAAA,EAErF,YAAY,QAA0C;AACpD,UAAM,MAAM;AACZ,SAAK,gBAAgB,OAAO;AAC5B,SAAK,mBAAmB,OAAO;AAC/B,SAAK,kBAAkB,OAAO;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,eAAe,UAA2B;AAChD,WAAO,KAAK,UAAU,YAAY,aAAa;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiB,UAA+C;AACtE,QAAI,KAAK,eAAe,QAAQ,GAAG;AACjC,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK,eAAe,IAAI,QAAQ,KAAK;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,aAAa,SAA+B,UAAwB;AAC1E,QAAI,KAAK,eAAe,QAAQ,GAAG;AACjC,WAAK,gBAAgB;AACrB,WAAK,SAAS,IAAI,kCAAmB,OAAO;AAC5C,WAAK,iBAAiB,QAAQ,OAAO;AAAA,IACvC,OAAO;AACL,WAAK,eAAe,IAAI,UAAU,OAAO;AACzC,WAAK,SAAS,IAAI,UAAU,OAAO;AACnC,WAAK,eAAe,IAAI,UAAU,QAAQ,OAAO;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAAkB,UAAwB;AAChD,QAAI,KAAK,eAAe,QAAQ,GAAG;AACjC,WAAK,gBAAgB;AACrB,WAAK,mBAAmB;AACxB,WAAK,SAAS,OAAO,gCAAiB;AAAA,IACxC,OAAO;AACL,WAAK,eAAe,OAAO,QAAQ;AACnC,WAAK,eAAe,OAAO,QAAQ;AACnC,WAAK,SAAS,OAAO,QAAQ;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,eAAe,SAA+B,UAAiC;AAE3F,SAAK,kBAAkB,QAAQ;AAE/B,SAAK,iBAAiB,OAAO,QAAQ;AAGrC,QAAI,QAAQ,YAAY;AACtB,UAAI;AACF,cAAM,QAAQ,WAAW,OAAO;AAAA,MAClC,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACF,YAAM,QAAQ,QAAQ,MAAM;AAAA,IAC9B,QAAQ;AAAA,IAER;AAGA,QAAI,QAAQ,eAAe;AACzB,UAAI;AACF,cAAM,QAAQ,cAAc,MAAM;AAAA,MACpC,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,cAAc,UAMzB;AACD,UAAM,UAAU,KAAK,cAAc,WAAW;AAE9C,SAAK,QAAQ,QAAQ,+BAA+B,QAAQ,+BAA+B,OAAO,EAAE;AAEpG,UAAM,gBAA6D;AAAA,MACjE,UAAU,KAAK,cAAc;AAAA,MAC7B,MAAM,CAAC,2BAA2B,OAAO,IAAI,kBAAkB,4BAA4B;AAAA,IAC7F;AAEA,QAAI,KAAK,cAAc,gBAAgB;AACrC,oBAAc,iBAAiB,KAAK,cAAc;AAAA,IACpD;AAGA,QAAI,gBAAsC;AAC1C,QAAI,UAA0B;AAE9B,QAAI;AAEF,sBAAgB,MAAM,gCAAS,aAAa,aAAa;AAGzD,YAAM,SAAS,KAAK,eAAe,aAAa;AAGhD,gBAAU,MAAM,gCAAS,QAAQ,cAAc,WAAW,CAAC;AAG3D,YAAM,UAAU,MAAM,QAAQ,WAAW;AAAA,QACvC,UAAU,KAAK,cAAc,YAAY,EAAE,OAAO,MAAM,QAAQ,IAAI;AAAA,MACtE,CAAC;AAED,YAAM,QAAQ,QAAQ;AAGtB,YAAM,QAAQ,QAAQ,MAAM;AAC5B,YAAM,aAAa,MAAM,CAAC,IAAI,MAAM,QAAQ,cAAc,MAAM,CAAC,CAAC,IAAI;AAMtE,UAAI,oBAAoB;AACxB,YAAM,mBAAmB,MAAM;AAC7B,YAAI,kBAAmB;AACvB,4BAAoB;AACpB,aAAK,0BAA0B,QAAQ;AAAA,MACzC;AAGA,oBAAc,GAAG,SAAS,gBAAgB;AAE1C,cAAQ,GAAG,gBAAgB,gBAAgB;AAK3C,UAAI;AACF,cAAM,oBAAoB,MAAM,QAAQ,qBAAqB;AAE7D,cAAM,kBAAkB,KAAK,6BAA6B,EAAE,UAAU,KAAK,CAAC;AAE5E,0BAAkB,GAAG,0BAA0B,YAAY;AAIzD,cAAI;AACF,kBAAM,EAAE,YAAY,IAAK,MAAM,kBAAkB,KAAK,mBAAmB;AAIzE,kBAAM,cAAc,YAAY;AAAA,cAC9B,OAAK,EAAE,SAAS,UAAU,CAAC,EAAE,IAAI,WAAW,WAAW,KAAK,CAAC,EAAE,IAAI,WAAW,aAAa;AAAA,YAC7F;AACA,gBAAI,YAAY,WAAW,GAAG;AAC5B,+BAAiB;AAAA,YACnB;AAAA,UACF,QAAQ;AAEN,6BAAiB;AAAA,UACnB;AAAA,QACF,CAAC;AAGD,0BAAkB,GAAG,sBAAsB,gBAAgB;AAAA,MAC7D,QAAQ;AAEN,aAAK,QAAQ,QAAQ,oDAAoD;AAAA,MAC3E;AAEA,aAAO,EAAE,eAAe,SAAS,SAAS,YAAY,OAAO;AAAA,IAC/D,SAAS,OAAO;AAEd,WAAK,QAAQ,OAAO,uCAAuC,QAAQ,KAAK,KAAK,EAAE;AAC/E,YAAM,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACrC,YAAM,eAAe,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC3C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,UAAkC;AACnD,UAAM,oBAAoB,YAAY;AACtC,WAAO,KAAK,iBAAiB,iBAAiB,GAAG,UAAU;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,uBAAuB,UAAyC;AACpE,UAAM,oBAAoB,YAAY;AACtC,UAAM,UAAU,KAAK,iBAAiB,iBAAiB;AAEvD,QAAI,CAAC,SAAS,SAAS;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,kBAAkB,QAAQ,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,SAAsC;AAC9D,UAAM,QAAQ,QAAQ,MAAM;AAC5B,WAAO,MAAM,MAAM,SAAS,CAAC,KAAK,MAAM,CAAC,KAAK;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,uBAAuB,UAA+C;AAC1E,UAAM,oBAAoB,YAAY;AACtC,UAAM,UAAU,KAAK,iBAAiB,iBAAiB;AAEvD,QAAI,CAAC,SAAS,SAAS;AACrB,aAAO;AAAA,IACT;AAGA,QAAI,QAAQ,WAAW,CAAC,QAAQ,QAAQ,YAAY,GAAG;AACrD,WAAK,0BAA0B,iBAAiB;AAChD,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,kBAAkB,QAAQ,OAAO;AAEzD,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAGA,UAAM,SAAS,KAAK,iBAAiB,IAAI,iBAAiB;AAC1D,UAAM,aAAa,WAAW,IAAI;AAClC,QAAI,UAAU,OAAO,YAAY,YAAY;AAE3C,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI;AACF,YAAM,aAAa,MAAM,QAAQ,QAAQ,cAAc,UAAU;AAEjE,WAAK,iBAAiB,IAAI,mBAAmB,EAAE,SAAS,YAAY,SAAS,WAAW,CAAC;AACzF,aAAO;AAAA,IACT,QAAQ;AAGN,WAAK,iBAAiB,OAAO,iBAAiB;AAC9C,WAAK,0BAA0B,iBAAiB;AAChD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAA0C;AAC5D,UAAM,oBAAoB,YAAY;AACtC,WAAO,KAAK,iBAAiB,iBAAiB,GAAG,WAAW;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBAAsB,UAA+C;AACzE,UAAM,oBAAoB,YAAY;AACtC,UAAM,UAAU,KAAK,iBAAiB,iBAAiB;AAEvD,QAAI,CAAC,SAAS,SAAS;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,KAAK,kBAAkB,QAAQ,OAAO;AACzD,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,MAAM,QAAQ,QAAQ,cAAc,UAAU;AAAA,IACvD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,cAAc,UAAiD;AAC7E,UAAM,aAAa,KAAK,qBAAqB,QAAQ;AACrD,UAAM,EAAE,eAAe,SAAS,SAAS,YAAY,OAAO,IAAI,MAAM,KAAK,cAAc,QAAQ;AAEjG,UAAM,UAAgC;AAAA,MACpC;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,MACpB,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,SAAK,aAAa,SAAS,QAAQ;AAEnC,SAAK,QAAQ,QAAQ,8BAA8B,QAAQ,cAAc,MAAM,EAAE;AAGjF,SAAK,mBAAmB,SAAS,UAAU,MAAM;AAEjD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,eAAe,eAA6C;AAElE,UAAM,cAAe,cAAmE;AAExF,QAAI,CAAC,aAAa;AAChB,WAAK,QAAQ,OAAO,8CAA8C;AAClE,aAAO;AAAA,IACT;AAEA,UAAM,mBAAe,uBAAK,aAAa,oBAAoB;AAG3D,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAC,2BAAW,YAAY,KAAK,KAAK,IAAI,IAAI,UAAU;AAEzD,cAAQ,KAAK,IAAI,WAAW,IAAI,kBAAkB,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE;AAAA,IACjE;AAEA,QAAI,KAAC,2BAAW,YAAY,GAAG;AAC7B,WAAK,QAAQ,OAAO,mCAAmC;AACvD,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,cAAU,6BAAa,cAAc,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI;AACrE,YAAM,OAAO,QAAQ,CAAC;AACtB,YAAM,cAAc,QAAQ,CAAC;AAE7B,UAAI,CAAC,QAAQ,CAAC,aAAa;AACzB,aAAK,QAAQ,OAAO,oCAAoC;AACxD,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,kBAAkB,IAAI,GAAG,WAAW;AACnD,WAAK,QAAQ,QAAQ,+CAA+C,MAAM,EAAE;AAC5E,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,2CAA2C,KAAK;AACpE,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,2BAA2B,QAA+B;AAC9D,QAAI,KAAK,eAAe;AACtB;AAAA,IACF;AACA,UAAM,KAAK,aAAa,QAAQ,gCAAiB;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBAAqC;AACzC,QAAI,KAAK,eAAe;AACtB;AAAA,IACF;AAEA,UAAM,EAAE,eAAe,SAAS,SAAS,YAAY,OAAO,IAAI,MAAM,KAAK,cAAc,gCAAiB;AAE1G,UAAM,UAAgC;AAAA,MACpC,UAAU;AAAA,MACV,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,SAAK,aAAa,SAAS,gCAAiB;AAE5C,SAAK,QAAQ,QAAQ,oCAAoC,MAAM,EAAE;AAGjE,SAAK,mBAAmB,SAAS,kCAAmB,MAAM;AAC1D,SAAK,mBAAmB,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAA0B,UAAwB;AACxD,SAAK,QAAQ,QAAQ,mCAAmC,QAAQ,EAAE;AAGlE,QAAI,CAAC,KAAK,iBAAiB,QAAQ,EAAG;AAGtC,SAAK,kBAAkB,QAAQ;AAC/B,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,qBAAqB,QAAgB,UAAiD;AAE1F,QAAI,KAAK,iBAAiB,QAAQ,GAAG;AACnC,UAAI,KAAK,eAAe,QAAQ,GAAG;AACjC,cAAM,KAAK,mBAAmB;AAAA,MAChC,OAAO;AACL,cAAM,KAAK,mBAAmB,QAAQ;AAAA,MACxC;AAAA,IACF;AAEA,WAAO,KAAK,aAAa,QAAQ,QAAQ;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,aAAa,QAAgB,UAAiD;AAC1F,UAAM,oBAAoB,KAAK,eAAe,QAAQ,IAAI,mCAAoB;AAC9E,SAAK,QAAQ,QAAQ,gCAAgC,iBAAiB,KAAK,MAAM,EAAE;AAEnF,QAAI,UAA0B;AAE9B,QAAI;AACF,gBAAU,MAAM,gCAAS,eAAe,MAAM;AAG9C,YAAM,WAAW,QAAQ,SAAS;AAClC,YAAM,UAAU,SAAS,CAAC,KAAM,MAAM,QAAQ,WAAW;AAGzD,UAAI,QAAQ,QAAQ,MAAM;AAC1B,UAAI,MAAM,WAAW,GAAG;AAEtB,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAG,CAAC;AACrD,gBAAQ,QAAQ,MAAM;AACtB,YAAI,MAAM,WAAW,GAAG;AACtB,gBAAM,QAAQ,QAAQ;AACtB,kBAAQ,QAAQ,MAAM;AAAA,QACxB;AAAA,MACF;AAGA,YAAM,aAAa,MAAM,CAAC,IAAI,MAAM,QAAQ,cAAc,MAAM,CAAC,CAAC,IAAI;AAGtE,UAAI,oBAAoB;AACxB,YAAM,mBAAmB,MAAM;AAC7B,YAAI,kBAAmB;AACvB,4BAAoB;AACpB,aAAK,0BAA0B,iBAAiB;AAAA,MAClD;AAGA,cAAQ,GAAG,SAAS,gBAAgB;AAEpC,cAAQ,GAAG,gBAAgB,gBAAgB;AAE3C,YAAM,UAAgC;AAAA,QACpC,UAAU;AAAA,QACV,WAAW,KAAK,IAAI;AAAA,QACpB,eAAe;AAAA;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,WAAK,aAAa,SAAS,QAAQ;AACnC,WAAK,QAAQ,QAAQ,+BAA+B,iBAAiB,EAAE;AAGvE,WAAK,mBAAmB,SAAS,mBAAmB,MAAM;AAC1D,WAAK,mBAAmB,OAAO;AAE/B,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,QAAQ,OAAO,6BAA6B,KAAK,EAAE;AACxD,YAAM,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACrC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB,UAAiC;AACxD,UAAM,UAAU,KAAK,eAAe,IAAI,QAAQ;AAChD,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AACA,UAAM,KAAK,eAAe,SAAS,QAAQ;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAoC;AACxC,QAAI,CAAC,KAAK,eAAe;AACvB;AAAA,IACF;AACA,UAAM,KAAK,eAAe,KAAK,eAAe,gCAAiB;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0B;AAE9B,UAAM,YAAY,MAAM,KAAK,KAAK,eAAe,KAAK,CAAC;AACvD,UAAM,QAAQ,IAAI,UAAU,IAAI,QAAM,KAAK,mBAAmB,EAAE,CAAC,CAAC;AAGlE,UAAM,KAAK,mBAAmB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,qBAAqB,SAAiC;AAC9D,UAAM,gBAAgB;AACtB,WAAO,cAAc;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,mBAA4B;AACpC,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI,MAAM,gEAAgE;AAAA,IAClF;AACA,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,iBAAiB,SAAuC;AACtE,UAAM,gBAAgB,KAAK,iBAAiB,QAAQ,QAAQ;AAC5D,QAAI,CAAC,eAAe;AAClB;AAAA,IACF;AACA,UAAM,KAAK,eAAe,eAAe,QAAQ,QAAQ;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA4B;AAC3C,UAAM,oBAAoB,YAAY;AACtC,WAAO,KAAK,iBAAiB,iBAAiB,MAAM;AAAA,EACtD;AACF;;;AD9oBO,IAAM,gBAAN,cAA4B,8BAAc;AAAA,EAC7B;AAAA,EACA,OAAO;AAAA,EACP,WAAW;AAAA,EACpB,eAAe;AAAA;AAAA,EAGf;AAAA;AAAA,EAGA;AAAA,EAKT,YAAY,QAA6B;AAGvC,UAAM,iBAAiB,OAAO,SAAU,OAAO,SAAS,WAAa,OAAO,SAAS;AAIrF,UAAM,EAAE,KAAK,MAAM,SAAS,UAAU,GAAG,WAAW,IAAI;AAExD,UAAM;AAAA,MACJ,GAAG;AAAA,MACH,OAAO;AAAA,IACT,CAAQ;AAER,SAAK,KAAK,kBAAkB,KAAK,IAAI,CAAC;AACtC,SAAK,MAAM,OAAO;AAClB,SAAK,eAAe;AAGpB,SAAK,gBAAgB,IAAI,2BAA2B;AAAA,MAClD,OAAO;AAAA,MACP,eAAe,EAAE,GAAG,QAAQ,UAAU,KAAK,SAAS;AAAA,MACpD,QAAQ,KAAK;AAAA,MACb,kBAAkB,aAAW;AAE3B,aAAK,mBAAmB,QAAQ,QAAQ;AAAA,MAC1C;AAAA,MACA,kBAAkB,CAAC,UAAU,UAAU,YAAY;AACjD,aAAK,QAAQ,QAAQ,8BAA8B,QAAQ,EAAE;AAAA,MAC/D;AAAA,MACA,iBAAiB,cAAY;AAC3B,aAAK,QAAQ,QAAQ,6BAA6B,QAAQ,EAAE;AAE5D,aAAK,oBAAoB,QAAQ;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcS,UAAU,UAAkC;AACnD,WAAO,KAAK,cAAc,mBAAmB,YAAY,KAAK,iBAAiB,CAAC;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAyB,WAA0B;AACjD,UAAM,QAAQ,KAAK,cAAc,SAAS;AAC1C,UAAM,SAAS,KAAK,OAAO;AAE3B,QAAI,QAAQ;AAEV,YAAM,MAAM,OAAO,WAAW,aAAa,MAAM,OAAO,IAAI;AAC5D,YAAM,KAAK,kBAAkB,GAAG;AAAA,IAClC,WAAW,UAAU,UAAU;AAE7B,YAAM,KAAK,cAAc,oBAAoB;AAAA,IAC/C;AAAA,EAEF;AAAA,EAEA,MAAyB,UAAyB;AAChD,UAAM,KAAK,cAAc,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,QAA+B;AAC7D,SAAK,QAAQ,QAAQ,qCAAqC,MAAM,EAAE;AAGlE,UAAM,KAAK,cAAc,2BAA2B,MAAM;AAE1D,SAAK,QAAQ,QAAQ,+BAA+B;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAe,cAA6B;AAC1C,UAAM,QAAQ,KAAK,cAAc,SAAS;AAC1C,UAAM,WAAW,KAAK,iBAAiB;AAGvC,QAAI,UAAU,YAAY,CAAC,KAAK,cAAc,iBAAiB,QAAQ,GAAG;AACxE,YAAM,KAAK,cAAc,oBAAoB,QAAQ;AAAA,IACvD;AAEA,UAAM,MAAM,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKS,iBAAiB,UAA4B;AACpD,WAAO,KAAK,cAAc,iBAAiB,YAAY,KAAK,iBAAiB,CAAC;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAe,OAAO,UAAkC;AACtD,UAAM,QAAQ,KAAK,cAAc,SAAS;AAC1C,UAAM,oBAAoB,YAAY,KAAK,iBAAiB;AAE5D,QAAI,UAAU,UAAU;AAEtB,UAAI,CAAC,KAAK,cAAc,iBAAiB,GAAG;AAC1C,cAAM,MAAM,OAAO;AAAA,MACrB;AAAA,IACF,OAAO;AAEL,UAAI,CAAC,KAAK,cAAc,iBAAiB,iBAAiB,GAAG;AAC3D,cAAM,KAAK,cAAc,oBAAoB,iBAAiB;AAG9D,aAAK,SAAS;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMS,4BAAkC;AAEzC,UAAM,0BAA0B;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAe,qBAAqB,QAAgB,UAAkC;AACpF,UAAM,oBAAoB,YAAY,KAAK,iBAAiB;AAC5D,UAAM,KAAK,cAAc,qBAAqB,QAAQ,iBAAiB;AAEvE,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAMA,MAAyB,cAAc,UAAyC;AAC9E,WAAO,KAAK,cAAc,uBAAuB,YAAY,KAAK,iBAAiB,CAAC;AAAA,EACtF;AAAA,EAEmB,yBAAyB,UAAwC;AAClF,UAAM,UAAU,KAAK,cAAc,oBAAoB,YAAY,KAAK,iBAAiB,CAAC;AAC1F,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,QAAQ,MAAM;AAE5B,UAAM,cAAc,MAAM,SAAS,IAAI,MAAM,SAAS,IAAI;AAC1D,UAAM,OAA0B,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,MAC1D,KAAK,KAAK,IAAI;AAAA,MACd,OAAO;AAAA;AAAA,MACP,UAAU,UAAU;AAAA,IACtB,EAAE;AAEF,WAAO;AAAA,MACL;AAAA,MACA,gBAAgB;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAe,gBAAgB,SAAwD;AACrF,UAAM,WAAW,SAAS,YAAY,KAAK,iBAAiB;AAK5D,UAAM,WAA+B;AAAA,MACnC,eAAe,YAAY;AACzB,cAAM,aAAa,MAAM,KAAK,cAAc,sBAAsB,QAAQ;AAC1E,YAAI,CAAC,YAAY;AACf,gBAAM,IAAI,MAAM,6CAA6C;AAAA,QAC/D;AAGA,eAAO;AAAA,UACL,MAAM,OAAO,QAAgB,WAAqC;AAChE,mBAAO,WAAW,KAAK,QAAe,MAAM;AAAA,UAC9C;AAAA,UACA,IAAI,CAAC,OAAe,YAAuC;AACzD,uBAAW,GAAG,OAAc,OAAO;AAAA,UACrC;AAAA,UACA,KAAK,CAAC,OAAe,YAAuC;AAC1D,uBAAW,IAAI,OAAc,OAAO;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,MACA,kBAAkB,MAAM,KAAK,iBAAiB,QAAQ;AAAA,IACxD;AAGA,UAAM,SAAS,IAAI,qCAAqB,UAAU;AAAA,MAChD,QAAQ,SAAS,UAAU;AAAA,MAC3B,SAAS,SAAS,WAAW;AAAA,MAC7B,UAAU,SAAS,YAAY;AAAA,MAC/B,WAAW,SAAS,aAAa;AAAA,MACjC,eAAe,SAAS,iBAAiB;AAAA,IAC3C,CAAC;AAGD,UAAM,UAAU,KAAK,cAAc,oBAAoB,QAAQ;AAC/D,QAAI,SAAS;AAEX,YAAM,gBAAgB,oBAAI,IAA8E;AAGxG,YAAM,YAAY,MAAM;AACtB,mBAAW,MAAM;AACf,cAAI,OAAO,SAAS,GAAG;AACrB,mBAAO,UAAU,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACnC;AAAA,QACF,GAAG,GAAG;AAAA,MACR;AAGA,YAAM,gBAAgB,CAAC,SAAe;AACpC,2BAAmB,IAAI;AAAA,MACzB;AAGA,YAAM,qBAAqB,CAAC,SAAe;AACzC,aAAK,KAAK,SAAS,MAAM;AAEvB,wBAAc,OAAO,IAAI;AACzB,qBAAW,MAAM;AACf,gBAAI,OAAO,SAAS,KAAK,QAAQ,MAAM,EAAE,SAAS,GAAG;AACnD,qBAAO,UAAU,EAAE,MAAM,MAAM;AAAA,cAAC,CAAC;AAAA,YACnC;AAAA,UACF,GAAG,GAAG;AAAA,QACR,CAAC;AAGD,cAAM,mBAAmB,CAAC,UAA6D;AACrF,cAAI,CAAC,MAAM,YAAY,GAAG;AACxB,mBAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,UAC5B;AAAA,QACF;AACA,aAAK,GAAG,kBAAkB,gBAAgB;AAC1C,sBAAc,IAAI,MAAM,gBAAgB;AAAA,MAC1C;AAGA,cAAQ,GAAG,QAAQ,SAAS;AAC5B,cAAQ,GAAG,QAAQ,aAAa;AAGhC,iBAAW,QAAQ,QAAQ,MAAM,GAAG;AAClC,2BAAmB,IAAI;AAAA,MACzB;AAGA,aAAO,KAAK,QAAQ,MAAM;AACxB,gBAAQ,IAAI,QAAQ,SAAS;AAC7B,gBAAQ,IAAI,QAAQ,aAAa;AAEjC,mBAAW,CAAC,MAAM,QAAQ,KAAK,eAAe;AAC5C,eAAK,IAAI,kBAAkB,QAAQ;AAAA,QACrC;AACA,sBAAc,MAAM;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,MAAM;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAMA,MAAe,iBAAiB,QAA0B,UAAkC;AAC1F,UAAM,aAAa,MAAM,KAAK,cAAc,uBAAuB,YAAY,KAAK,iBAAiB,CAAC;AACtG,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAEA,UAAM,WAAW,KAAK,4BAA4B,MAAM;AAAA,EAC1D;AAAA,EAEA,MAAe,oBAAoB,QAA6B,UAAkC;AAChG,UAAM,aAAa,MAAM,KAAK,cAAc,uBAAuB,YAAY,KAAK,iBAAiB,CAAC;AACtG,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAEA,UAAM,WAAW,KAAK,0BAA0B,MAAM;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAMA,WAAiC;AAG/B,WAAO,CAAC;AAAA,EACV;AACF;","names":["import_browser"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -26,11 +26,6 @@ interface BrowserViewerConfig extends BrowserConfigBase {
|
|
|
26
26
|
* @default 0 (auto-assign available port)
|
|
27
27
|
*/
|
|
28
28
|
cdpPort?: number;
|
|
29
|
-
/**
|
|
30
|
-
* Path to Chrome user data directory (profile).
|
|
31
|
-
* Persists cookies, localStorage, extensions, etc.
|
|
32
|
-
*/
|
|
33
|
-
userDataDir?: string;
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -26,11 +26,6 @@ interface BrowserViewerConfig extends BrowserConfigBase {
|
|
|
26
26
|
* @default 0 (auto-assign available port)
|
|
27
27
|
*/
|
|
28
28
|
cdpPort?: number;
|
|
29
|
-
/**
|
|
30
|
-
* Path to Chrome user data directory (profile).
|
|
31
|
-
* Persists cookies, localStorage, extensions, etc.
|
|
32
|
-
*/
|
|
33
|
-
userDataDir?: string;
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
/**
|
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ var BrowserViewerThreadManager = class extends ThreadManager {
|
|
|
114
114
|
const cdpPort = this.browserConfig.cdpPort ?? 0;
|
|
115
115
|
this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);
|
|
116
116
|
const launchOptions = {
|
|
117
|
-
headless: this.browserConfig.headless
|
|
117
|
+
headless: this.browserConfig.headless,
|
|
118
118
|
args: [`--remote-debugging-port=${cdpPort}`, "--no-first-run", "--no-default-browser-check"]
|
|
119
119
|
};
|
|
120
120
|
if (this.browserConfig.executablePath) {
|
|
@@ -511,7 +511,7 @@ var BrowserViewer = class extends MastraBrowser {
|
|
|
511
511
|
viewerConfig;
|
|
512
512
|
constructor(config) {
|
|
513
513
|
const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
|
|
514
|
-
const { cli: _cli, cdpPort: _cdpPort,
|
|
514
|
+
const { cli: _cli, cdpPort: _cdpPort, ...baseConfig } = config;
|
|
515
515
|
super({
|
|
516
516
|
...baseConfig,
|
|
517
517
|
scope: effectiveScope
|
|
@@ -521,7 +521,7 @@ var BrowserViewer = class extends MastraBrowser {
|
|
|
521
521
|
this.viewerConfig = config;
|
|
522
522
|
this.threadManager = new BrowserViewerThreadManager({
|
|
523
523
|
scope: effectiveScope,
|
|
524
|
-
browserConfig: config,
|
|
524
|
+
browserConfig: { ...config, headless: this.headless },
|
|
525
525
|
logger: this.logger,
|
|
526
526
|
onSessionCreated: (session) => {
|
|
527
527
|
this.notifyBrowserReady(session.threadId);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser-viewer.ts","../src/thread-manager.ts"],"sourcesContent":["/**\n * BrowserViewer - Playwright-managed Chrome for CLI providers\n *\n * Launches Chrome via Playwright and exposes the CDP URL for CLI tools\n * (agent-browser, browser-use, browse-cli) to connect as secondary clients.\n *\n * This gives us:\n * - Direct page-level CDP sessions (fixes screencast sessionId issues)\n * - Full browser lifecycle control\n * - Predictable CDP URL for CLI injection\n * - Thread-scoped browser isolation\n */\n\nimport { MastraBrowser, ScreencastStreamImpl } from '@mastra/core/browser';\nimport type {\n BrowserState,\n BrowserTabState,\n ScreencastOptions,\n ScreencastStream,\n CdpSessionProvider,\n MouseEventParams,\n KeyboardEventParams,\n} from '@mastra/core/browser';\nimport type { Tool } from '@mastra/core/tools';\nimport type { Page } from 'playwright-core';\nimport { BrowserViewerThreadManager } from './thread-manager';\nimport type { BrowserViewerConfig, CLIProvider } from './types';\n\n/**\n * BrowserViewer - CLI provider with Playwright-managed Chrome\n *\n * Use this with Workspace to enable browser automation via CLI tools.\n * The agent uses skills + workspace_execute_command to drive the CLI,\n * while Mastra handles screencast, input injection, and lifecycle.\n *\n * @example\n * ```ts\n * import { Workspace } from '@mastra/core';\n * import { BrowserViewer } from '@mastra/browser-viewer';\n *\n * const workspace = new Workspace({\n * browser: new BrowserViewer({\n * cli: 'agent-browser',\n * headless: false,\n * }),\n * });\n * ```\n */\nexport class BrowserViewer extends MastraBrowser {\n override readonly id: string;\n override readonly name = 'BrowserViewer';\n override readonly provider = 'browser-viewer';\n readonly providerType = 'cli' as const;\n\n /** Which CLI the agent uses */\n readonly cli: CLIProvider;\n\n /** Viewer-specific config (stored for reference) */\n readonly viewerConfig: BrowserViewerConfig;\n\n /** Thread manager for browser sessions */\n declare protected threadManager: BrowserViewerThreadManager;\n\n constructor(config: BrowserViewerConfig) {\n // Default to 'thread' scope (each thread gets its own Chrome)\n // Use 'shared' if connecting to an existing browser\n const effectiveScope = config.cdpUrl ? (config.scope ?? 'shared') : (config.scope ?? 'thread');\n\n // Build base config (exclude CLI-specific options)\n // Use type assertion because BrowserConfig is a discriminated union\n const { cli: _cli, cdpPort: _cdpPort, userDataDir: _userDataDir, ...baseConfig } = config;\n\n super({\n ...baseConfig,\n scope: effectiveScope,\n } as any);\n\n this.id = `browser-viewer-${Date.now()}`;\n this.cli = config.cli;\n this.viewerConfig = config;\n\n // Initialize thread manager\n this.threadManager = new BrowserViewerThreadManager({\n scope: effectiveScope,\n browserConfig: config,\n logger: this.logger,\n onSessionCreated: session => {\n // Notify listeners so screencast can start for this thread\n this.notifyBrowserReady(session.threadId);\n },\n onBrowserCreated: (_browser, threadId, _cdpUrl) => {\n this.logger?.debug?.(`Browser created for thread ${threadId}`);\n },\n onBrowserClosed: threadId => {\n this.logger?.debug?.(`Browser closed for thread ${threadId}`);\n // Notify base class callbacks so ViewerRegistry gets notified\n this.notifyBrowserClosed(threadId);\n },\n });\n }\n\n // ---------------------------------------------------------------------------\n // CDP URL Access\n // ---------------------------------------------------------------------------\n\n /**\n * Get the CDP WebSocket URL for CLI tools to connect.\n * For thread scope, returns the CDP URL for the specified thread.\n * For shared scope, returns the single shared CDP URL.\n *\n * @param threadId - Thread identifier (optional, uses current thread if not specified)\n * @returns CDP URL or null if browser not running for that thread\n */\n override getCdpUrl(threadId?: string): string | null {\n return this.threadManager.getCdpUrlForThread(threadId ?? this.getCurrentThread());\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async doLaunch(): Promise<void> {\n const scope = this.threadManager.getScope();\n const cdpUrl = this.config.cdpUrl;\n\n if (cdpUrl) {\n // Connect mode: connect to existing browser (always shared)\n const url = typeof cdpUrl === 'function' ? await cdpUrl() : cdpUrl;\n await this.connectToExisting(url);\n } else if (scope === 'shared') {\n // Shared mode: launch single browser\n await this.threadManager.createSharedSession();\n }\n // For thread scope, browsers are launched lazily per thread via ensureReady()\n }\n\n protected override async doClose(): Promise<void> {\n await this.threadManager.closeAll();\n }\n\n /**\n * Connect to an existing browser via CDP URL.\n */\n private async connectToExisting(cdpUrl: string): Promise<void> {\n this.logger?.debug?.(`Connecting to existing browser at ${cdpUrl}`);\n\n // Create a shared session from the external CDP connection\n await this.threadManager.createSharedSessionFromCdp(cdpUrl);\n\n this.logger?.debug?.('Connected to existing browser');\n }\n\n /**\n * Ensure browser is ready for the current thread.\n * For thread scope, creates a new browser if needed.\n */\n override async ensureReady(): Promise<void> {\n const scope = this.threadManager.getScope();\n const threadId = this.getCurrentThread();\n\n // For thread scope, create browser for this thread if needed\n if (scope === 'thread' && !this.threadManager.isBrowserRunning(threadId)) {\n await this.threadManager.getManagerForThread(threadId);\n }\n\n await super.ensureReady();\n }\n\n /**\n * Check if browser is running (for current thread in thread scope).\n */\n override isBrowserRunning(threadId?: string): boolean {\n return this.threadManager.isBrowserRunning(threadId ?? this.getCurrentThread());\n }\n\n /**\n * Launch browser, optionally for a specific thread.\n * For thread scope, creates a browser for that thread.\n * For shared scope, launches the single shared browser.\n */\n override async launch(threadId?: string): Promise<void> {\n const scope = this.threadManager.getScope();\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n\n if (scope === 'shared') {\n // For shared scope, use base class launch (handles racing, status, etc.)\n if (!this.threadManager.isBrowserRunning()) {\n await super.launch();\n }\n } else {\n // For thread scope, launch for this specific thread\n if (!this.threadManager.isBrowserRunning(effectiveThreadId)) {\n await this.threadManager.getManagerForThread(effectiveThreadId);\n // Set status to ready so isBrowserRunning() returns true\n // (base class launch() does this, but we bypass it for thread scope)\n this.status = 'ready';\n }\n }\n }\n\n /**\n * Handle browser disconnection.\n * Overrides base class method.\n */\n override handleBrowserDisconnected(): void {\n // Call parent to handle status and notifications\n super.handleBrowserDisconnected();\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * Use this when an agent is using their own external CDP (e.g., browser-use cloud).\n * Connects Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n override async connectToExternalCdp(cdpUrl: string, threadId?: string): Promise<void> {\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n await this.threadManager.connectToExternalCdp(cdpUrl, effectiveThreadId);\n // Mark as ready\n this.status = 'ready';\n }\n\n // ---------------------------------------------------------------------------\n // Browser State (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async getActivePage(threadId?: string): Promise<Page | null> {\n return this.threadManager.getActivePageForThread(threadId ?? this.getCurrentThread());\n }\n\n protected override getBrowserStateForThread(threadId?: string): BrowserState | null {\n const context = this.threadManager.getContextForThread(threadId ?? this.getCurrentThread());\n if (!context) {\n return null;\n }\n\n const pages = context.pages();\n // Active page is the last one (most recently opened), consistent with resolveActivePage\n const activeIndex = pages.length > 0 ? pages.length - 1 : 0;\n const tabs: BrowserTabState[] = pages.map((page, index) => ({\n url: page.url(),\n title: '', // Would need async call to get title\n isActive: index === activeIndex,\n }));\n\n return {\n tabs,\n activeTabIndex: activeIndex,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Screencast Support\n // ---------------------------------------------------------------------------\n\n override async startScreencast(options?: ScreencastOptions): Promise<ScreencastStream> {\n const threadId = options?.threadId ?? this.getCurrentThread();\n\n // Create CDP session provider that creates FRESH sessions on each call\n // This is critical for tab switching - when reconnecting, we need a CDP session\n // attached to the CURRENT page, not the original page from launch\n const provider: CdpSessionProvider = {\n getCdpSession: async () => {\n const cdpSession = await this.threadManager.createFreshCdpSession(threadId);\n if (!cdpSession) {\n throw new Error('No browser context available for screencast');\n }\n\n // Return wrapper that implements CdpSessionLike\n return {\n send: async (method: string, params?: Record<string, unknown>) => {\n return cdpSession.send(method as any, params);\n },\n on: (event: string, handler: (params: unknown) => void) => {\n cdpSession.on(event as any, handler);\n },\n off: (event: string, handler: (params: unknown) => void) => {\n cdpSession.off(event as any, handler);\n },\n };\n },\n isBrowserRunning: () => this.isBrowserRunning(threadId),\n };\n\n // Create and start screencast stream\n const stream = new ScreencastStreamImpl(provider, {\n format: options?.format ?? 'jpeg',\n quality: options?.quality ?? 80,\n maxWidth: options?.maxWidth ?? 1280,\n maxHeight: options?.maxHeight ?? 720,\n everyNthFrame: options?.everyNthFrame ?? 1,\n });\n\n // Set up tab change detection - reconnect screencast when tabs change\n const context = this.threadManager.getContextForThread(threadId);\n if (context) {\n // Track all listeners for cleanup\n const pageListeners = new Map<Page, (frame: { url: () => string; parentFrame: () => unknown }) => void>();\n\n // New tab opened - reconnect screencast\n const onNewPage = () => {\n setTimeout(() => {\n if (stream.isActive()) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n };\n\n // Handler for new pages that sets up listeners\n const onPageCreated = (page: Page) => {\n setupPageListeners(page);\n };\n\n // Set up page close listener for each page\n const setupPageListeners = (page: Page) => {\n page.once('close', () => {\n // Clean up this page's listener\n pageListeners.delete(page);\n setTimeout(() => {\n if (stream.isActive() && context.pages().length > 0) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n });\n\n // Navigation listener for URL updates\n const onFrameNavigated = (frame: { url: () => string; parentFrame: () => unknown }) => {\n if (!frame.parentFrame()) {\n stream.emitUrl(frame.url());\n }\n };\n page.on('framenavigated', onFrameNavigated);\n pageListeners.set(page, onFrameNavigated);\n };\n\n // Set up listeners\n context.on('page', onNewPage);\n context.on('page', onPageCreated);\n\n // Set up for existing pages\n for (const page of context.pages()) {\n setupPageListeners(page);\n }\n\n // Clean up all listeners on stream stop\n stream.once('stop', () => {\n context.off('page', onNewPage);\n context.off('page', onPageCreated);\n // Remove framenavigated listeners from all pages\n for (const [page, listener] of pageListeners) {\n page.off('framenavigated', listener);\n }\n pageListeners.clear();\n });\n }\n\n await stream.start();\n return stream;\n }\n\n // ---------------------------------------------------------------------------\n // Input Injection\n // ---------------------------------------------------------------------------\n\n override async injectMouseEvent(params: MouseEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for mouse injection');\n }\n\n await cdpSession.send('Input.dispatchMouseEvent', params);\n }\n\n override async injectKeyboardEvent(params: KeyboardEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for keyboard injection');\n }\n\n await cdpSession.send('Input.dispatchKeyEvent', params);\n }\n\n // ---------------------------------------------------------------------------\n // Tools (CLI agents don't use SDK tools - they use workspace commands)\n // ---------------------------------------------------------------------------\n\n getTools(): Record<string, Tool> {\n // CLI agents use workspace_execute_command with CLI skills\n // No SDK tools needed\n return {};\n }\n}\n","/**\n * BrowserViewerThreadManager - Thread scope management for BrowserViewer\n *\n * Manages thread-scoped browser sessions using Playwright to launch\n * separate Chrome instances per thread.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { ThreadManager, DEFAULT_THREAD_ID } from '@mastra/core/browser';\nimport type { ThreadSession, ThreadManagerConfig } from '@mastra/core/browser';\nimport { chromium } from 'playwright-core';\nimport type { Browser, BrowserContext, BrowserServer, CDPSession, Page } from 'playwright-core';\nimport type { BrowserViewerConfig } from './types';\n\n/**\n * Extended session info for BrowserViewer.\n */\ninterface BrowserViewerSession extends ThreadSession {\n /**\n * Playwright browser server (owns the Chrome process).\n * Null for external CDP connections where we don't own the browser process.\n */\n browserServer: BrowserServer | null;\n /** Playwright browser instance (connected to server) */\n browser: Browser;\n /** Browser context */\n context: BrowserContext;\n /** CDP session for the active page */\n cdpSession: CDPSession | null;\n /** CDP WebSocket URL (null if discovery failed) */\n cdpUrl: string | null;\n}\n\n/**\n * Configuration for BrowserViewerThreadManager.\n */\nexport interface BrowserViewerThreadManagerConfig extends ThreadManagerConfig {\n /** Browser configuration */\n browserConfig: BrowserViewerConfig;\n /** Callback when a browser is created for a thread */\n onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n /** Callback when a browser is closed for a thread */\n onBrowserClosed?: (threadId: string) => void;\n}\n\n/**\n * Thread manager implementation for BrowserViewer.\n *\n * Supports two scope modes:\n * - 'shared': All threads share one Chrome instance\n * - 'thread': Each thread gets a dedicated Chrome instance\n */\nexport class BrowserViewerThreadManager extends ThreadManager<Browser> {\n private readonly browserConfig: BrowserViewerConfig;\n private readonly onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n private readonly onBrowserClosed?: (threadId: string) => void;\n\n /** Map of thread ID to session info (for 'thread' scope) */\n private readonly threadSessions = new Map<string, BrowserViewerSession>();\n\n /** Shared session info (for 'shared' scope) */\n private sharedSession: BrowserViewerSession | null = null;\n\n /** Cached CDP sessions for input injection, keyed by threadId */\n private inputCdpSessions = new Map<string, { session: CDPSession; pageUrl: string }>();\n\n constructor(config: BrowserViewerThreadManagerConfig) {\n super(config);\n this.browserConfig = config.browserConfig;\n this.onBrowserCreated = config.onBrowserCreated;\n this.onBrowserClosed = config.onBrowserClosed;\n }\n\n /**\n * Check if a thread should use the shared session slot.\n * In shared scope, all threads use the shared session.\n * In thread scope, DEFAULT_THREAD_ID also uses the shared session.\n */\n private usesSharedSlot(threadId: string): boolean {\n return this.scope === 'shared' || threadId === DEFAULT_THREAD_ID;\n }\n\n /**\n * Get the viewer session for a thread, using consistent routing.\n * Handles both shared and thread-scoped sessions.\n */\n private getViewerSession(threadId: string): BrowserViewerSession | null {\n if (this.usesSharedSlot(threadId)) {\n return this.sharedSession;\n }\n return this.threadSessions.get(threadId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Session Storage & Cleanup Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Store a session in the appropriate slot based on scope.\n * Consolidates session storage logic used by createSession, createSharedSession,\n * createSharedSessionFromCdp, and connectToExternalCdp.\n */\n private storeSession(session: BrowserViewerSession, threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = session;\n this.sessions.set(DEFAULT_THREAD_ID, session);\n this.setSharedManager(session.browser);\n } else {\n this.threadSessions.set(threadId, session);\n this.sessions.set(threadId, session);\n this.threadManagers.set(threadId, session.browser);\n }\n }\n\n /**\n * Clear a session from the appropriate slot based on scope.\n * Must be called BEFORE async cleanup operations to prevent double callbacks\n * from disconnect handlers.\n */\n private clearSessionState(threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = null;\n this.clearSharedManager();\n this.sessions.delete(DEFAULT_THREAD_ID);\n } else {\n this.threadSessions.delete(threadId);\n this.threadManagers.delete(threadId);\n this.sessions.delete(threadId);\n }\n }\n\n /**\n * Clean up a session's resources (CDP session, browser, server).\n * Consolidates cleanup logic used by closeThreadBrowser, closeSharedBrowser,\n * and doDestroySession.\n *\n * @param session - The session to clean up\n * @param threadId - The thread ID (for onBrowserClosed callback)\n */\n private async cleanupSession(session: BrowserViewerSession, threadId: string): Promise<void> {\n // Clear state BEFORE async operations to prevent double callback from disconnect handler\n this.clearSessionState(threadId);\n // Clear cached input CDP session\n this.inputCdpSessions.delete(threadId);\n\n // Detach CDP session\n if (session.cdpSession) {\n try {\n await session.cdpSession.detach();\n } catch {\n // Ignore - session may already be detached\n }\n }\n\n // Close browser connection\n try {\n await session.browser.close();\n } catch {\n // Ignore - browser may already be closed\n }\n\n // Close browser server (kills the Chrome process) - only if we own it\n if (session.browserServer) {\n try {\n await session.browserServer.close();\n } catch {\n // Ignore - server may already be closed\n }\n }\n\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Launch a new browser instance and return the components.\n * Consolidates the launch logic shared by createSession and createSharedSession.\n *\n * @param threadId - Thread ID for logging and disconnect handler\n */\n private async launchBrowser(threadId: string): Promise<{\n browserServer: BrowserServer;\n browser: Browser;\n context: BrowserContext;\n cdpSession: CDPSession | null;\n cdpUrl: string | null;\n }> {\n const cdpPort = this.browserConfig.cdpPort ?? 0;\n\n this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);\n\n const launchOptions: Parameters<typeof chromium.launchServer>[0] = {\n headless: this.browserConfig.headless ?? false,\n args: [`--remote-debugging-port=${cdpPort}`, '--no-first-run', '--no-default-browser-check'],\n };\n\n if (this.browserConfig.executablePath) {\n launchOptions.executablePath = this.browserConfig.executablePath;\n }\n\n // Track partially initialized resources for cleanup on failure\n let browserServer: BrowserServer | null = null;\n let browser: Browser | null = null;\n\n try {\n // Launch server - this starts Chrome\n browserServer = await chromium.launchServer(launchOptions);\n\n // Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file\n const cdpUrl = this.discoverCdpUrl(browserServer);\n\n // Connect to the browser via Playwright for screencast/session management\n browser = await chromium.connect(browserServer.wsEndpoint());\n\n // Create context and initial page\n const context = await browser.newContext({\n viewport: this.browserConfig.viewport ?? { width: 1280, height: 720 },\n });\n\n await context.newPage();\n\n // Set up CDP session for active page (used for screencast/input injection)\n const pages = context.pages();\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - multiple events can indicate browser closure:\n // - browserServer.on('close'): fires when Chrome process exits\n // - browser.on('disconnected'): fires when Playwright connection is lost\n // - CDP Target.targetDestroyed: fires when any target (page/context) is destroyed\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(threadId);\n };\n\n // Listen for browser server close (fires when Chrome process exits)\n browserServer.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n // Use browser-level CDP session to watch for ALL target destruction\n // Page-level CDP session only sees events for that specific page, but CLI creates its own pages\n // Browser-level session sees all targets across all contexts\n try {\n const browserCdpSession = await browser.newBrowserCDPSession();\n // Enable target discovery to get notified of all targets\n await browserCdpSession.send('Target.setDiscoverTargets', { discover: true });\n\n browserCdpSession.on('Target.targetDestroyed', async () => {\n // When a target is destroyed, check if any page targets remain\n // browser.isConnected() stays true because browserServer keeps Chrome alive,\n // so we need to check for actual page targets instead\n try {\n const { targetInfos } = (await browserCdpSession.send('Target.getTargets')) as {\n targetInfos: Array<{ type: string; url: string }>;\n };\n // Filter to actual page targets (not background pages, service workers, etc.)\n const pageTargets = targetInfos.filter(\n t => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'),\n );\n if (pageTargets.length === 0) {\n handleDisconnect();\n }\n } catch {\n // CDP session dead, browser definitely closed\n handleDisconnect();\n }\n });\n\n // Also listen for detached event (fires when CDP connection is lost)\n browserCdpSession.on('Inspector.detached', handleDisconnect);\n } catch {\n // Non-fatal: target watching is a reliability enhancement, not required\n this.logger?.debug?.('Failed to set up browser-level CDP target watching');\n }\n\n return { browserServer, browser, context, cdpSession, cdpUrl };\n } catch (error) {\n // Clean up partially initialized resources\n this.logger?.warn?.(`Failed to launch browser for thread ${threadId}: ${error}`);\n await browser?.close().catch(() => {});\n await browserServer?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Get CDP URL for a specific thread.\n */\n getCdpUrlForThread(threadId?: string): string | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.cdpUrl ?? null;\n }\n\n /**\n * Get the active page for a thread.\n */\n async getActivePageForThread(threadId?: string): Promise<Page | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n return this.resolveActivePage(session.context);\n }\n\n /**\n * Resolve the active page from a browser context.\n * Uses last page (most recently opened) with fallback to first page.\n */\n private resolveActivePage(context: BrowserContext): Page | null {\n const pages = context.pages();\n return pages[pages.length - 1] ?? pages[0] ?? null;\n }\n\n /**\n * Get or create a CDP session for the active page in a thread.\n *\n * CDP sessions are page-scoped, so we create a fresh one for the currently active page\n * rather than caching one that may point to a closed or inactive page.\n */\n async getCdpSessionForThread(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n // Check if browser is still connected - if not, trigger cleanup\n if (session.browser && !session.browser.isConnected()) {\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n\n if (!activePage) {\n return null;\n }\n\n // Check if we have a cached CDP session for the current active page\n const cached = this.inputCdpSessions.get(effectiveThreadId);\n const currentUrl = activePage.url();\n if (cached && cached.pageUrl === currentUrl) {\n // Reuse cached session if same page\n return cached.session;\n }\n\n // Create a new CDP session for the active page\n try {\n const cdpSession = await session.context.newCDPSession(activePage);\n // Cache it for future input events\n this.inputCdpSessions.set(effectiveThreadId, { session: cdpSession, pageUrl: currentUrl });\n return cdpSession;\n } catch {\n // Page may have been closed between getting pages and creating session\n // This often indicates browser was closed - trigger cleanup\n this.inputCdpSessions.delete(effectiveThreadId);\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n }\n\n /**\n * Get the browser context for a thread.\n */\n getContextForThread(threadId?: string): BrowserContext | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.context ?? null;\n }\n\n /**\n * Create a fresh CDP session for the active page (not cached).\n * Used by screencast which needs fresh sessions on tab switches.\n */\n async createFreshCdpSession(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n if (!activePage) {\n return null;\n }\n\n try {\n return await session.context.newCDPSession(activePage);\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session for a thread.\n */\n protected async createSession(threadId: string): Promise<BrowserViewerSession> {\n const savedState = this.getSavedBrowserState(threadId);\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(threadId);\n\n const session: BrowserViewerSession = {\n threadId,\n createdAt: Date.now(),\n browserState: savedState,\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, threadId);\n\n this.logger?.debug?.(`Chrome launched for thread ${threadId}, CDP URL: ${cdpUrl}`);\n\n // Notify callback\n this.onBrowserCreated?.(browser, threadId, cdpUrl);\n\n return session;\n }\n\n /**\n * Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file.\n *\n * Playwright's BrowserServer exposes _userDataDirForTest which points to Chrome's\n * user data directory. Chrome writes a DevToolsActivePort file there containing:\n * Line 1: The debugging port number\n * Line 2: The browser WebSocket path (e.g., /devtools/browser/<guid>)\n *\n * This gives us the real CDP URL that external tools like agent-browser can connect to.\n * Returns null if discovery fails - callers should handle this case.\n */\n private discoverCdpUrl(browserServer: BrowserServer): string | null {\n // Access Playwright's internal user data directory\n const userDataDir = (browserServer as BrowserServer & { _userDataDirForTest?: string })._userDataDirForTest;\n\n if (!userDataDir) {\n this.logger?.warn?.('Could not access browser user data directory');\n return null;\n }\n\n const portFilePath = join(userDataDir, 'DevToolsActivePort');\n\n // Chrome may still be writing the file during startup - retry with a short deadline\n const deadline = Date.now() + 1500;\n while (!existsSync(portFilePath) && Date.now() < deadline) {\n // Use Atomics.wait for a non-blocking ~50ms sleep\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n\n if (!existsSync(portFilePath)) {\n this.logger?.warn?.('DevToolsActivePort file not found');\n return null;\n }\n\n try {\n const content = readFileSync(portFilePath, 'utf-8').trim().split('\\n');\n const port = content[0];\n const browserPath = content[1];\n\n if (!port || !browserPath) {\n this.logger?.warn?.('Invalid DevToolsActivePort content');\n return null;\n }\n\n const cdpUrl = `ws://127.0.0.1:${port}${browserPath}`;\n this.logger?.debug?.(`Discovered CDP URL from DevToolsActivePort: ${cdpUrl}`);\n return cdpUrl;\n } catch (error) {\n this.logger?.warn?.('Failed to read DevToolsActivePort file:', error);\n return null;\n }\n }\n\n /**\n * Create a shared session by connecting to an existing browser via CDP URL.\n * Used when BrowserViewer is configured with a cdpUrl to connect to an external browser.\n */\n async createSharedSessionFromCdp(cdpUrl: string): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n await this.connectToCdp(cdpUrl, DEFAULT_THREAD_ID);\n }\n\n /**\n * Create a shared session (for 'shared' scope).\n */\n async createSharedSession(): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(DEFAULT_THREAD_ID);\n\n const session: BrowserViewerSession = {\n threadId: DEFAULT_THREAD_ID,\n createdAt: Date.now(),\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, DEFAULT_THREAD_ID);\n\n this.logger?.debug?.(`Shared Chrome launched, CDP URL: ${cdpUrl}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, DEFAULT_THREAD_ID, cdpUrl);\n this.onSessionCreated?.(session);\n }\n\n /**\n * Handle browser disconnection for a thread.\n */\n private handleBrowserDisconnected(threadId: string): void {\n this.logger?.debug?.(`Browser disconnected for thread ${threadId}`);\n\n // Guard against already-closed session (browser.close() triggers 'disconnected')\n if (!this.getViewerSession(threadId)) return;\n\n // Use consolidated helper for state cleanup\n this.clearSessionState(threadId);\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * This is used when an agent is using their own external CDP (e.g., browser-use cloud).\n * We connect Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n async connectToExternalCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n // Close any existing session for this thread to avoid leaking browser processes\n if (this.getViewerSession(threadId)) {\n if (this.usesSharedSlot(threadId)) {\n await this.closeSharedBrowser();\n } else {\n await this.closeThreadBrowser(threadId);\n }\n }\n\n return this.connectToCdp(cdpUrl, threadId);\n }\n\n /**\n * Connect to a browser via CDP URL and create a session.\n * Shared implementation for createSharedSessionFromCdp and connectToExternalCdp.\n */\n private async connectToCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n const effectiveThreadId = this.usesSharedSlot(threadId) ? DEFAULT_THREAD_ID : threadId;\n this.logger?.debug?.(`Connecting to CDP for thread ${effectiveThreadId}: ${cdpUrl}`);\n\n let browser: Browser | null = null;\n\n try {\n browser = await chromium.connectOverCDP(cdpUrl);\n\n // Get or create context\n const contexts = browser.contexts();\n const context = contexts[0] ?? (await browser.newContext());\n\n // Get or create page\n let pages = context.pages();\n if (pages.length === 0) {\n // Wait briefly for external browser to create a page, or create one\n await new Promise(resolve => setTimeout(resolve, 500));\n pages = context.pages();\n if (pages.length === 0) {\n await context.newPage();\n pages = context.pages();\n }\n }\n\n // Set up CDP session for active page\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - use effectiveThreadId for consistent lifecycle callbacks\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(effectiveThreadId);\n };\n\n // Listen for context close (fires when browser window is closed manually)\n context.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n const session: BrowserViewerSession = {\n threadId: effectiveThreadId,\n createdAt: Date.now(),\n browserServer: null, // We don't own the server for external CDP connections\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n this.storeSession(session, threadId);\n this.logger?.debug?.(`Connected to CDP for thread ${effectiveThreadId}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, effectiveThreadId, cdpUrl);\n this.onSessionCreated?.(session);\n\n return session;\n } catch (error) {\n this.logger?.warn?.(`Failed to connect to CDP: ${error}`);\n await browser?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Close a specific thread's browser.\n */\n async closeThreadBrowser(threadId: string): Promise<void> {\n const session = this.threadSessions.get(threadId);\n if (!session) {\n return;\n }\n await this.cleanupSession(session, threadId);\n }\n\n /**\n * Close the shared browser.\n */\n async closeSharedBrowser(): Promise<void> {\n if (!this.sharedSession) {\n return;\n }\n await this.cleanupSession(this.sharedSession, DEFAULT_THREAD_ID);\n }\n\n /**\n * Close all browsers.\n */\n async closeAll(): Promise<void> {\n // Close all thread browsers\n const threadIds = Array.from(this.threadSessions.keys());\n await Promise.all(threadIds.map(id => this.closeThreadBrowser(id)));\n\n // Close shared browser\n await this.closeSharedBrowser();\n }\n\n /**\n * Get the manager for a session.\n * Required by base class.\n */\n protected getManagerForSession(session: ThreadSession): Browser {\n const viewerSession = session as BrowserViewerSession;\n return viewerSession.browser;\n }\n\n /**\n * Get the shared manager.\n * Required by base class.\n */\n protected getSharedManager(): Browser {\n if (!this.sharedSession) {\n throw new Error('Shared browser not launched. Call createSharedSession() first.');\n }\n return this.sharedSession.browser;\n }\n\n /**\n * Destroy a session and clean up resources.\n * Required by base class.\n */\n protected async doDestroySession(session: ThreadSession): Promise<void> {\n const viewerSession = this.getViewerSession(session.threadId);\n if (!viewerSession) {\n return;\n }\n await this.cleanupSession(viewerSession, session.threadId);\n }\n\n /**\n * Check if browser is running for a thread.\n */\n isBrowserRunning(threadId?: string): boolean {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId) !== null;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser-viewer.ts","../src/thread-manager.ts"],"sourcesContent":["/**\n * BrowserViewer - Playwright-managed Chrome for CLI providers\n *\n * Launches Chrome via Playwright and exposes the CDP URL for CLI tools\n * (agent-browser, browser-use, browse-cli) to connect as secondary clients.\n *\n * This gives us:\n * - Direct page-level CDP sessions (fixes screencast sessionId issues)\n * - Full browser lifecycle control\n * - Predictable CDP URL for CLI injection\n * - Thread-scoped browser isolation\n */\n\nimport { MastraBrowser, ScreencastStreamImpl } from '@mastra/core/browser';\nimport type {\n BrowserState,\n BrowserTabState,\n ScreencastOptions,\n ScreencastStream,\n CdpSessionProvider,\n MouseEventParams,\n KeyboardEventParams,\n} from '@mastra/core/browser';\nimport type { Tool } from '@mastra/core/tools';\nimport type { Page } from 'playwright-core';\nimport { BrowserViewerThreadManager } from './thread-manager';\nimport type { BrowserViewerConfig, CLIProvider } from './types';\n\n/**\n * BrowserViewer - CLI provider with Playwright-managed Chrome\n *\n * Use this with Workspace to enable browser automation via CLI tools.\n * The agent uses skills + workspace_execute_command to drive the CLI,\n * while Mastra handles screencast, input injection, and lifecycle.\n *\n * @example\n * ```ts\n * import { Workspace } from '@mastra/core';\n * import { BrowserViewer } from '@mastra/browser-viewer';\n *\n * const workspace = new Workspace({\n * browser: new BrowserViewer({\n * cli: 'agent-browser',\n * headless: false,\n * }),\n * });\n * ```\n */\nexport class BrowserViewer extends MastraBrowser {\n override readonly id: string;\n override readonly name = 'BrowserViewer';\n override readonly provider = 'browser-viewer';\n readonly providerType = 'cli' as const;\n\n /** Which CLI the agent uses */\n readonly cli: CLIProvider;\n\n /** Viewer-specific config (stored for reference) */\n readonly viewerConfig: BrowserViewerConfig;\n\n /** Thread manager for browser sessions */\n declare protected threadManager: BrowserViewerThreadManager;\n\n constructor(config: BrowserViewerConfig) {\n // Default to 'thread' scope (each thread gets its own Chrome)\n // Use 'shared' if connecting to an existing browser\n const effectiveScope = config.cdpUrl ? (config.scope ?? 'shared') : (config.scope ?? 'thread');\n\n // Build base config (exclude CLI-specific options)\n // Use type assertion because BrowserConfig is a discriminated union\n const { cli: _cli, cdpPort: _cdpPort, ...baseConfig } = config;\n\n super({\n ...baseConfig,\n scope: effectiveScope,\n } as any);\n\n this.id = `browser-viewer-${Date.now()}`;\n this.cli = config.cli;\n this.viewerConfig = config;\n\n // Initialize thread manager\n this.threadManager = new BrowserViewerThreadManager({\n scope: effectiveScope,\n browserConfig: { ...config, headless: this.headless },\n logger: this.logger,\n onSessionCreated: session => {\n // Notify listeners so screencast can start for this thread\n this.notifyBrowserReady(session.threadId);\n },\n onBrowserCreated: (_browser, threadId, _cdpUrl) => {\n this.logger?.debug?.(`Browser created for thread ${threadId}`);\n },\n onBrowserClosed: threadId => {\n this.logger?.debug?.(`Browser closed for thread ${threadId}`);\n // Notify base class callbacks so ViewerRegistry gets notified\n this.notifyBrowserClosed(threadId);\n },\n });\n }\n\n // ---------------------------------------------------------------------------\n // CDP URL Access\n // ---------------------------------------------------------------------------\n\n /**\n * Get the CDP WebSocket URL for CLI tools to connect.\n * For thread scope, returns the CDP URL for the specified thread.\n * For shared scope, returns the single shared CDP URL.\n *\n * @param threadId - Thread identifier (optional, uses current thread if not specified)\n * @returns CDP URL or null if browser not running for that thread\n */\n override getCdpUrl(threadId?: string): string | null {\n return this.threadManager.getCdpUrlForThread(threadId ?? this.getCurrentThread());\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async doLaunch(): Promise<void> {\n const scope = this.threadManager.getScope();\n const cdpUrl = this.config.cdpUrl;\n\n if (cdpUrl) {\n // Connect mode: connect to existing browser (always shared)\n const url = typeof cdpUrl === 'function' ? await cdpUrl() : cdpUrl;\n await this.connectToExisting(url);\n } else if (scope === 'shared') {\n // Shared mode: launch single browser\n await this.threadManager.createSharedSession();\n }\n // For thread scope, browsers are launched lazily per thread via ensureReady()\n }\n\n protected override async doClose(): Promise<void> {\n await this.threadManager.closeAll();\n }\n\n /**\n * Connect to an existing browser via CDP URL.\n */\n private async connectToExisting(cdpUrl: string): Promise<void> {\n this.logger?.debug?.(`Connecting to existing browser at ${cdpUrl}`);\n\n // Create a shared session from the external CDP connection\n await this.threadManager.createSharedSessionFromCdp(cdpUrl);\n\n this.logger?.debug?.('Connected to existing browser');\n }\n\n /**\n * Ensure browser is ready for the current thread.\n * For thread scope, creates a new browser if needed.\n */\n override async ensureReady(): Promise<void> {\n const scope = this.threadManager.getScope();\n const threadId = this.getCurrentThread();\n\n // For thread scope, create browser for this thread if needed\n if (scope === 'thread' && !this.threadManager.isBrowserRunning(threadId)) {\n await this.threadManager.getManagerForThread(threadId);\n }\n\n await super.ensureReady();\n }\n\n /**\n * Check if browser is running (for current thread in thread scope).\n */\n override isBrowserRunning(threadId?: string): boolean {\n return this.threadManager.isBrowserRunning(threadId ?? this.getCurrentThread());\n }\n\n /**\n * Launch browser, optionally for a specific thread.\n * For thread scope, creates a browser for that thread.\n * For shared scope, launches the single shared browser.\n */\n override async launch(threadId?: string): Promise<void> {\n const scope = this.threadManager.getScope();\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n\n if (scope === 'shared') {\n // For shared scope, use base class launch (handles racing, status, etc.)\n if (!this.threadManager.isBrowserRunning()) {\n await super.launch();\n }\n } else {\n // For thread scope, launch for this specific thread\n if (!this.threadManager.isBrowserRunning(effectiveThreadId)) {\n await this.threadManager.getManagerForThread(effectiveThreadId);\n // Set status to ready so isBrowserRunning() returns true\n // (base class launch() does this, but we bypass it for thread scope)\n this.status = 'ready';\n }\n }\n }\n\n /**\n * Handle browser disconnection.\n * Overrides base class method.\n */\n override handleBrowserDisconnected(): void {\n // Call parent to handle status and notifications\n super.handleBrowserDisconnected();\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * Use this when an agent is using their own external CDP (e.g., browser-use cloud).\n * Connects Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n override async connectToExternalCdp(cdpUrl: string, threadId?: string): Promise<void> {\n const effectiveThreadId = threadId ?? this.getCurrentThread();\n await this.threadManager.connectToExternalCdp(cdpUrl, effectiveThreadId);\n // Mark as ready\n this.status = 'ready';\n }\n\n // ---------------------------------------------------------------------------\n // Browser State (implements MastraBrowser abstract methods)\n // ---------------------------------------------------------------------------\n\n protected override async getActivePage(threadId?: string): Promise<Page | null> {\n return this.threadManager.getActivePageForThread(threadId ?? this.getCurrentThread());\n }\n\n protected override getBrowserStateForThread(threadId?: string): BrowserState | null {\n const context = this.threadManager.getContextForThread(threadId ?? this.getCurrentThread());\n if (!context) {\n return null;\n }\n\n const pages = context.pages();\n // Active page is the last one (most recently opened), consistent with resolveActivePage\n const activeIndex = pages.length > 0 ? pages.length - 1 : 0;\n const tabs: BrowserTabState[] = pages.map((page, index) => ({\n url: page.url(),\n title: '', // Would need async call to get title\n isActive: index === activeIndex,\n }));\n\n return {\n tabs,\n activeTabIndex: activeIndex,\n };\n }\n\n // ---------------------------------------------------------------------------\n // Screencast Support\n // ---------------------------------------------------------------------------\n\n override async startScreencast(options?: ScreencastOptions): Promise<ScreencastStream> {\n const threadId = options?.threadId ?? this.getCurrentThread();\n\n // Create CDP session provider that creates FRESH sessions on each call\n // This is critical for tab switching - when reconnecting, we need a CDP session\n // attached to the CURRENT page, not the original page from launch\n const provider: CdpSessionProvider = {\n getCdpSession: async () => {\n const cdpSession = await this.threadManager.createFreshCdpSession(threadId);\n if (!cdpSession) {\n throw new Error('No browser context available for screencast');\n }\n\n // Return wrapper that implements CdpSessionLike\n return {\n send: async (method: string, params?: Record<string, unknown>) => {\n return cdpSession.send(method as any, params);\n },\n on: (event: string, handler: (params: unknown) => void) => {\n cdpSession.on(event as any, handler);\n },\n off: (event: string, handler: (params: unknown) => void) => {\n cdpSession.off(event as any, handler);\n },\n };\n },\n isBrowserRunning: () => this.isBrowserRunning(threadId),\n };\n\n // Create and start screencast stream\n const stream = new ScreencastStreamImpl(provider, {\n format: options?.format ?? 'jpeg',\n quality: options?.quality ?? 80,\n maxWidth: options?.maxWidth ?? 1280,\n maxHeight: options?.maxHeight ?? 720,\n everyNthFrame: options?.everyNthFrame ?? 1,\n });\n\n // Set up tab change detection - reconnect screencast when tabs change\n const context = this.threadManager.getContextForThread(threadId);\n if (context) {\n // Track all listeners for cleanup\n const pageListeners = new Map<Page, (frame: { url: () => string; parentFrame: () => unknown }) => void>();\n\n // New tab opened - reconnect screencast\n const onNewPage = () => {\n setTimeout(() => {\n if (stream.isActive()) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n };\n\n // Handler for new pages that sets up listeners\n const onPageCreated = (page: Page) => {\n setupPageListeners(page);\n };\n\n // Set up page close listener for each page\n const setupPageListeners = (page: Page) => {\n page.once('close', () => {\n // Clean up this page's listener\n pageListeners.delete(page);\n setTimeout(() => {\n if (stream.isActive() && context.pages().length > 0) {\n stream.reconnect().catch(() => {});\n }\n }, 100);\n });\n\n // Navigation listener for URL updates\n const onFrameNavigated = (frame: { url: () => string; parentFrame: () => unknown }) => {\n if (!frame.parentFrame()) {\n stream.emitUrl(frame.url());\n }\n };\n page.on('framenavigated', onFrameNavigated);\n pageListeners.set(page, onFrameNavigated);\n };\n\n // Set up listeners\n context.on('page', onNewPage);\n context.on('page', onPageCreated);\n\n // Set up for existing pages\n for (const page of context.pages()) {\n setupPageListeners(page);\n }\n\n // Clean up all listeners on stream stop\n stream.once('stop', () => {\n context.off('page', onNewPage);\n context.off('page', onPageCreated);\n // Remove framenavigated listeners from all pages\n for (const [page, listener] of pageListeners) {\n page.off('framenavigated', listener);\n }\n pageListeners.clear();\n });\n }\n\n await stream.start();\n return stream;\n }\n\n // ---------------------------------------------------------------------------\n // Input Injection\n // ---------------------------------------------------------------------------\n\n override async injectMouseEvent(params: MouseEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for mouse injection');\n }\n\n await cdpSession.send('Input.dispatchMouseEvent', params);\n }\n\n override async injectKeyboardEvent(params: KeyboardEventParams, threadId?: string): Promise<void> {\n const cdpSession = await this.threadManager.getCdpSessionForThread(threadId ?? this.getCurrentThread());\n if (!cdpSession) {\n throw new Error('CDP session not available for keyboard injection');\n }\n\n await cdpSession.send('Input.dispatchKeyEvent', params);\n }\n\n // ---------------------------------------------------------------------------\n // Tools (CLI agents don't use SDK tools - they use workspace commands)\n // ---------------------------------------------------------------------------\n\n getTools(): Record<string, Tool> {\n // CLI agents use workspace_execute_command with CLI skills\n // No SDK tools needed\n return {};\n }\n}\n","/**\n * BrowserViewerThreadManager - Thread scope management for BrowserViewer\n *\n * Manages thread-scoped browser sessions using Playwright to launch\n * separate Chrome instances per thread.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { ThreadManager, DEFAULT_THREAD_ID } from '@mastra/core/browser';\nimport type { ThreadSession, ThreadManagerConfig } from '@mastra/core/browser';\nimport { chromium } from 'playwright-core';\nimport type { Browser, BrowserContext, BrowserServer, CDPSession, Page } from 'playwright-core';\nimport type { BrowserViewerConfig } from './types';\n\n/**\n * Extended session info for BrowserViewer.\n */\ninterface BrowserViewerSession extends ThreadSession {\n /**\n * Playwright browser server (owns the Chrome process).\n * Null for external CDP connections where we don't own the browser process.\n */\n browserServer: BrowserServer | null;\n /** Playwright browser instance (connected to server) */\n browser: Browser;\n /** Browser context */\n context: BrowserContext;\n /** CDP session for the active page */\n cdpSession: CDPSession | null;\n /** CDP WebSocket URL (null if discovery failed) */\n cdpUrl: string | null;\n}\n\n/**\n * Configuration for BrowserViewerThreadManager.\n */\nexport interface BrowserViewerThreadManagerConfig extends ThreadManagerConfig {\n /** Browser configuration */\n browserConfig: BrowserViewerConfig;\n /** Callback when a browser is created for a thread */\n onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n /** Callback when a browser is closed for a thread */\n onBrowserClosed?: (threadId: string) => void;\n}\n\n/**\n * Thread manager implementation for BrowserViewer.\n *\n * Supports two scope modes:\n * - 'shared': All threads share one Chrome instance\n * - 'thread': Each thread gets a dedicated Chrome instance\n */\nexport class BrowserViewerThreadManager extends ThreadManager<Browser> {\n private readonly browserConfig: BrowserViewerConfig;\n private readonly onBrowserCreated?: (browser: Browser, threadId: string, cdpUrl: string | null) => void;\n private readonly onBrowserClosed?: (threadId: string) => void;\n\n /** Map of thread ID to session info (for 'thread' scope) */\n private readonly threadSessions = new Map<string, BrowserViewerSession>();\n\n /** Shared session info (for 'shared' scope) */\n private sharedSession: BrowserViewerSession | null = null;\n\n /** Cached CDP sessions for input injection, keyed by threadId */\n private inputCdpSessions = new Map<string, { session: CDPSession; pageUrl: string }>();\n\n constructor(config: BrowserViewerThreadManagerConfig) {\n super(config);\n this.browserConfig = config.browserConfig;\n this.onBrowserCreated = config.onBrowserCreated;\n this.onBrowserClosed = config.onBrowserClosed;\n }\n\n /**\n * Check if a thread should use the shared session slot.\n * In shared scope, all threads use the shared session.\n * In thread scope, DEFAULT_THREAD_ID also uses the shared session.\n */\n private usesSharedSlot(threadId: string): boolean {\n return this.scope === 'shared' || threadId === DEFAULT_THREAD_ID;\n }\n\n /**\n * Get the viewer session for a thread, using consistent routing.\n * Handles both shared and thread-scoped sessions.\n */\n private getViewerSession(threadId: string): BrowserViewerSession | null {\n if (this.usesSharedSlot(threadId)) {\n return this.sharedSession;\n }\n return this.threadSessions.get(threadId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Session Storage & Cleanup Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Store a session in the appropriate slot based on scope.\n * Consolidates session storage logic used by createSession, createSharedSession,\n * createSharedSessionFromCdp, and connectToExternalCdp.\n */\n private storeSession(session: BrowserViewerSession, threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = session;\n this.sessions.set(DEFAULT_THREAD_ID, session);\n this.setSharedManager(session.browser);\n } else {\n this.threadSessions.set(threadId, session);\n this.sessions.set(threadId, session);\n this.threadManagers.set(threadId, session.browser);\n }\n }\n\n /**\n * Clear a session from the appropriate slot based on scope.\n * Must be called BEFORE async cleanup operations to prevent double callbacks\n * from disconnect handlers.\n */\n private clearSessionState(threadId: string): void {\n if (this.usesSharedSlot(threadId)) {\n this.sharedSession = null;\n this.clearSharedManager();\n this.sessions.delete(DEFAULT_THREAD_ID);\n } else {\n this.threadSessions.delete(threadId);\n this.threadManagers.delete(threadId);\n this.sessions.delete(threadId);\n }\n }\n\n /**\n * Clean up a session's resources (CDP session, browser, server).\n * Consolidates cleanup logic used by closeThreadBrowser, closeSharedBrowser,\n * and doDestroySession.\n *\n * @param session - The session to clean up\n * @param threadId - The thread ID (for onBrowserClosed callback)\n */\n private async cleanupSession(session: BrowserViewerSession, threadId: string): Promise<void> {\n // Clear state BEFORE async operations to prevent double callback from disconnect handler\n this.clearSessionState(threadId);\n // Clear cached input CDP session\n this.inputCdpSessions.delete(threadId);\n\n // Detach CDP session\n if (session.cdpSession) {\n try {\n await session.cdpSession.detach();\n } catch {\n // Ignore - session may already be detached\n }\n }\n\n // Close browser connection\n try {\n await session.browser.close();\n } catch {\n // Ignore - browser may already be closed\n }\n\n // Close browser server (kills the Chrome process) - only if we own it\n if (session.browserServer) {\n try {\n await session.browserServer.close();\n } catch {\n // Ignore - server may already be closed\n }\n }\n\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Launch a new browser instance and return the components.\n * Consolidates the launch logic shared by createSession and createSharedSession.\n *\n * @param threadId - Thread ID for logging and disconnect handler\n */\n private async launchBrowser(threadId: string): Promise<{\n browserServer: BrowserServer;\n browser: Browser;\n context: BrowserContext;\n cdpSession: CDPSession | null;\n cdpUrl: string | null;\n }> {\n const cdpPort = this.browserConfig.cdpPort ?? 0;\n\n this.logger?.debug?.(`Launching Chrome for thread ${threadId} with remote-debugging-port=${cdpPort}`);\n\n const launchOptions: Parameters<typeof chromium.launchServer>[0] = {\n headless: this.browserConfig.headless,\n args: [`--remote-debugging-port=${cdpPort}`, '--no-first-run', '--no-default-browser-check'],\n };\n\n if (this.browserConfig.executablePath) {\n launchOptions.executablePath = this.browserConfig.executablePath;\n }\n\n // Track partially initialized resources for cleanup on failure\n let browserServer: BrowserServer | null = null;\n let browser: Browser | null = null;\n\n try {\n // Launch server - this starts Chrome\n browserServer = await chromium.launchServer(launchOptions);\n\n // Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file\n const cdpUrl = this.discoverCdpUrl(browserServer);\n\n // Connect to the browser via Playwright for screencast/session management\n browser = await chromium.connect(browserServer.wsEndpoint());\n\n // Create context and initial page\n const context = await browser.newContext({\n viewport: this.browserConfig.viewport ?? { width: 1280, height: 720 },\n });\n\n await context.newPage();\n\n // Set up CDP session for active page (used for screencast/input injection)\n const pages = context.pages();\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - multiple events can indicate browser closure:\n // - browserServer.on('close'): fires when Chrome process exits\n // - browser.on('disconnected'): fires when Playwright connection is lost\n // - CDP Target.targetDestroyed: fires when any target (page/context) is destroyed\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(threadId);\n };\n\n // Listen for browser server close (fires when Chrome process exits)\n browserServer.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n // Use browser-level CDP session to watch for ALL target destruction\n // Page-level CDP session only sees events for that specific page, but CLI creates its own pages\n // Browser-level session sees all targets across all contexts\n try {\n const browserCdpSession = await browser.newBrowserCDPSession();\n // Enable target discovery to get notified of all targets\n await browserCdpSession.send('Target.setDiscoverTargets', { discover: true });\n\n browserCdpSession.on('Target.targetDestroyed', async () => {\n // When a target is destroyed, check if any page targets remain\n // browser.isConnected() stays true because browserServer keeps Chrome alive,\n // so we need to check for actual page targets instead\n try {\n const { targetInfos } = (await browserCdpSession.send('Target.getTargets')) as {\n targetInfos: Array<{ type: string; url: string }>;\n };\n // Filter to actual page targets (not background pages, service workers, etc.)\n const pageTargets = targetInfos.filter(\n t => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'),\n );\n if (pageTargets.length === 0) {\n handleDisconnect();\n }\n } catch {\n // CDP session dead, browser definitely closed\n handleDisconnect();\n }\n });\n\n // Also listen for detached event (fires when CDP connection is lost)\n browserCdpSession.on('Inspector.detached', handleDisconnect);\n } catch {\n // Non-fatal: target watching is a reliability enhancement, not required\n this.logger?.debug?.('Failed to set up browser-level CDP target watching');\n }\n\n return { browserServer, browser, context, cdpSession, cdpUrl };\n } catch (error) {\n // Clean up partially initialized resources\n this.logger?.warn?.(`Failed to launch browser for thread ${threadId}: ${error}`);\n await browser?.close().catch(() => {});\n await browserServer?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Get CDP URL for a specific thread.\n */\n getCdpUrlForThread(threadId?: string): string | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.cdpUrl ?? null;\n }\n\n /**\n * Get the active page for a thread.\n */\n async getActivePageForThread(threadId?: string): Promise<Page | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n return this.resolveActivePage(session.context);\n }\n\n /**\n * Resolve the active page from a browser context.\n * Uses last page (most recently opened) with fallback to first page.\n */\n private resolveActivePage(context: BrowserContext): Page | null {\n const pages = context.pages();\n return pages[pages.length - 1] ?? pages[0] ?? null;\n }\n\n /**\n * Get or create a CDP session for the active page in a thread.\n *\n * CDP sessions are page-scoped, so we create a fresh one for the currently active page\n * rather than caching one that may point to a closed or inactive page.\n */\n async getCdpSessionForThread(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n // Check if browser is still connected - if not, trigger cleanup\n if (session.browser && !session.browser.isConnected()) {\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n\n if (!activePage) {\n return null;\n }\n\n // Check if we have a cached CDP session for the current active page\n const cached = this.inputCdpSessions.get(effectiveThreadId);\n const currentUrl = activePage.url();\n if (cached && cached.pageUrl === currentUrl) {\n // Reuse cached session if same page\n return cached.session;\n }\n\n // Create a new CDP session for the active page\n try {\n const cdpSession = await session.context.newCDPSession(activePage);\n // Cache it for future input events\n this.inputCdpSessions.set(effectiveThreadId, { session: cdpSession, pageUrl: currentUrl });\n return cdpSession;\n } catch {\n // Page may have been closed between getting pages and creating session\n // This often indicates browser was closed - trigger cleanup\n this.inputCdpSessions.delete(effectiveThreadId);\n this.handleBrowserDisconnected(effectiveThreadId);\n return null;\n }\n }\n\n /**\n * Get the browser context for a thread.\n */\n getContextForThread(threadId?: string): BrowserContext | null {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId)?.context ?? null;\n }\n\n /**\n * Create a fresh CDP session for the active page (not cached).\n * Used by screencast which needs fresh sessions on tab switches.\n */\n async createFreshCdpSession(threadId?: string): Promise<CDPSession | null> {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n const session = this.getViewerSession(effectiveThreadId);\n\n if (!session?.context) {\n return null;\n }\n\n const activePage = this.resolveActivePage(session.context);\n if (!activePage) {\n return null;\n }\n\n try {\n return await session.context.newCDPSession(activePage);\n } catch {\n return null;\n }\n }\n\n /**\n * Create a new session for a thread.\n */\n protected async createSession(threadId: string): Promise<BrowserViewerSession> {\n const savedState = this.getSavedBrowserState(threadId);\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(threadId);\n\n const session: BrowserViewerSession = {\n threadId,\n createdAt: Date.now(),\n browserState: savedState,\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, threadId);\n\n this.logger?.debug?.(`Chrome launched for thread ${threadId}, CDP URL: ${cdpUrl}`);\n\n // Notify callback\n this.onBrowserCreated?.(browser, threadId, cdpUrl);\n\n return session;\n }\n\n /**\n * Discover the actual CDP WebSocket URL from Chrome's DevToolsActivePort file.\n *\n * Playwright's BrowserServer exposes _userDataDirForTest which points to Chrome's\n * user data directory. Chrome writes a DevToolsActivePort file there containing:\n * Line 1: The debugging port number\n * Line 2: The browser WebSocket path (e.g., /devtools/browser/<guid>)\n *\n * This gives us the real CDP URL that external tools like agent-browser can connect to.\n * Returns null if discovery fails - callers should handle this case.\n */\n private discoverCdpUrl(browserServer: BrowserServer): string | null {\n // Access Playwright's internal user data directory\n const userDataDir = (browserServer as BrowserServer & { _userDataDirForTest?: string })._userDataDirForTest;\n\n if (!userDataDir) {\n this.logger?.warn?.('Could not access browser user data directory');\n return null;\n }\n\n const portFilePath = join(userDataDir, 'DevToolsActivePort');\n\n // Chrome may still be writing the file during startup - retry with a short deadline\n const deadline = Date.now() + 1500;\n while (!existsSync(portFilePath) && Date.now() < deadline) {\n // Use Atomics.wait for a non-blocking ~50ms sleep\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);\n }\n\n if (!existsSync(portFilePath)) {\n this.logger?.warn?.('DevToolsActivePort file not found');\n return null;\n }\n\n try {\n const content = readFileSync(portFilePath, 'utf-8').trim().split('\\n');\n const port = content[0];\n const browserPath = content[1];\n\n if (!port || !browserPath) {\n this.logger?.warn?.('Invalid DevToolsActivePort content');\n return null;\n }\n\n const cdpUrl = `ws://127.0.0.1:${port}${browserPath}`;\n this.logger?.debug?.(`Discovered CDP URL from DevToolsActivePort: ${cdpUrl}`);\n return cdpUrl;\n } catch (error) {\n this.logger?.warn?.('Failed to read DevToolsActivePort file:', error);\n return null;\n }\n }\n\n /**\n * Create a shared session by connecting to an existing browser via CDP URL.\n * Used when BrowserViewer is configured with a cdpUrl to connect to an external browser.\n */\n async createSharedSessionFromCdp(cdpUrl: string): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n await this.connectToCdp(cdpUrl, DEFAULT_THREAD_ID);\n }\n\n /**\n * Create a shared session (for 'shared' scope).\n */\n async createSharedSession(): Promise<void> {\n if (this.sharedSession) {\n return; // Already created\n }\n\n const { browserServer, browser, context, cdpSession, cdpUrl } = await this.launchBrowser(DEFAULT_THREAD_ID);\n\n const session: BrowserViewerSession = {\n threadId: DEFAULT_THREAD_ID,\n createdAt: Date.now(),\n browserServer,\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n // Store session using consolidated helper\n this.storeSession(session, DEFAULT_THREAD_ID);\n\n this.logger?.debug?.(`Shared Chrome launched, CDP URL: ${cdpUrl}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, DEFAULT_THREAD_ID, cdpUrl);\n this.onSessionCreated?.(session);\n }\n\n /**\n * Handle browser disconnection for a thread.\n */\n private handleBrowserDisconnected(threadId: string): void {\n this.logger?.debug?.(`Browser disconnected for thread ${threadId}`);\n\n // Guard against already-closed session (browser.close() triggers 'disconnected')\n if (!this.getViewerSession(threadId)) return;\n\n // Use consolidated helper for state cleanup\n this.clearSessionState(threadId);\n this.onBrowserClosed?.(threadId);\n }\n\n /**\n * Connect to an external browser via CDP URL for screencast.\n *\n * This is used when an agent is using their own external CDP (e.g., browser-use cloud).\n * We connect Playwright to the external browser to enable screencast without launching\n * our own browser.\n *\n * @param cdpUrl - The external CDP WebSocket URL (wss://... or ws://...)\n * @param threadId - Thread ID to associate the session with\n */\n async connectToExternalCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n // Close any existing session for this thread to avoid leaking browser processes\n if (this.getViewerSession(threadId)) {\n if (this.usesSharedSlot(threadId)) {\n await this.closeSharedBrowser();\n } else {\n await this.closeThreadBrowser(threadId);\n }\n }\n\n return this.connectToCdp(cdpUrl, threadId);\n }\n\n /**\n * Connect to a browser via CDP URL and create a session.\n * Shared implementation for createSharedSessionFromCdp and connectToExternalCdp.\n */\n private async connectToCdp(cdpUrl: string, threadId: string): Promise<BrowserViewerSession> {\n const effectiveThreadId = this.usesSharedSlot(threadId) ? DEFAULT_THREAD_ID : threadId;\n this.logger?.debug?.(`Connecting to CDP for thread ${effectiveThreadId}: ${cdpUrl}`);\n\n let browser: Browser | null = null;\n\n try {\n browser = await chromium.connectOverCDP(cdpUrl);\n\n // Get or create context\n const contexts = browser.contexts();\n const context = contexts[0] ?? (await browser.newContext());\n\n // Get or create page\n let pages = context.pages();\n if (pages.length === 0) {\n // Wait briefly for external browser to create a page, or create one\n await new Promise(resolve => setTimeout(resolve, 500));\n pages = context.pages();\n if (pages.length === 0) {\n await context.newPage();\n pages = context.pages();\n }\n }\n\n // Set up CDP session for active page\n const cdpSession = pages[0] ? await context.newCDPSession(pages[0]) : null;\n\n // Set up disconnection handlers - use effectiveThreadId for consistent lifecycle callbacks\n let disconnectHandled = false;\n const handleDisconnect = () => {\n if (disconnectHandled) return;\n disconnectHandled = true;\n this.handleBrowserDisconnected(effectiveThreadId);\n };\n\n // Listen for context close (fires when browser window is closed manually)\n context.on('close', handleDisconnect);\n // Listen for browser connection lost\n browser.on('disconnected', handleDisconnect);\n\n const session: BrowserViewerSession = {\n threadId: effectiveThreadId,\n createdAt: Date.now(),\n browserServer: null, // We don't own the server for external CDP connections\n browser,\n context,\n cdpSession,\n cdpUrl,\n };\n\n this.storeSession(session, threadId);\n this.logger?.debug?.(`Connected to CDP for thread ${effectiveThreadId}`);\n\n // Notify callbacks\n this.onBrowserCreated?.(browser, effectiveThreadId, cdpUrl);\n this.onSessionCreated?.(session);\n\n return session;\n } catch (error) {\n this.logger?.warn?.(`Failed to connect to CDP: ${error}`);\n await browser?.close().catch(() => {});\n throw error;\n }\n }\n\n /**\n * Close a specific thread's browser.\n */\n async closeThreadBrowser(threadId: string): Promise<void> {\n const session = this.threadSessions.get(threadId);\n if (!session) {\n return;\n }\n await this.cleanupSession(session, threadId);\n }\n\n /**\n * Close the shared browser.\n */\n async closeSharedBrowser(): Promise<void> {\n if (!this.sharedSession) {\n return;\n }\n await this.cleanupSession(this.sharedSession, DEFAULT_THREAD_ID);\n }\n\n /**\n * Close all browsers.\n */\n async closeAll(): Promise<void> {\n // Close all thread browsers\n const threadIds = Array.from(this.threadSessions.keys());\n await Promise.all(threadIds.map(id => this.closeThreadBrowser(id)));\n\n // Close shared browser\n await this.closeSharedBrowser();\n }\n\n /**\n * Get the manager for a session.\n * Required by base class.\n */\n protected getManagerForSession(session: ThreadSession): Browser {\n const viewerSession = session as BrowserViewerSession;\n return viewerSession.browser;\n }\n\n /**\n * Get the shared manager.\n * Required by base class.\n */\n protected getSharedManager(): Browser {\n if (!this.sharedSession) {\n throw new Error('Shared browser not launched. Call createSharedSession() first.');\n }\n return this.sharedSession.browser;\n }\n\n /**\n * Destroy a session and clean up resources.\n * Required by base class.\n */\n protected async doDestroySession(session: ThreadSession): Promise<void> {\n const viewerSession = this.getViewerSession(session.threadId);\n if (!viewerSession) {\n return;\n }\n await this.cleanupSession(viewerSession, session.threadId);\n }\n\n /**\n * Check if browser is running for a thread.\n */\n isBrowserRunning(threadId?: string): boolean {\n const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n return this.getViewerSession(effectiveThreadId) !== null;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/browser-viewer",
|
|
3
|
-
"version": "0.1.0",
|
|
3
|
+
"version": "0.1.1-alpha.0",
|
|
4
4
|
"description": "Playwright-based browser viewer for Mastra CLI providers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "22.15.21",
|
|
32
|
-
"@vitest/coverage-v8": "4.1.
|
|
33
|
-
"@vitest/ui": "4.1.
|
|
32
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
33
|
+
"@vitest/ui": "4.1.5",
|
|
34
34
|
"eslint": "^9.27.0",
|
|
35
35
|
"tsup": "^8.5.0",
|
|
36
36
|
"typescript": "^5.9.3",
|
|
37
|
-
"vitest": "4.1.
|
|
37
|
+
"vitest": "4.1.5",
|
|
38
38
|
"zod": "^4.3.6",
|
|
39
39
|
"@internal/lint": "0.0.85",
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
40
|
+
"@internal/types-builder": "0.0.60",
|
|
41
|
+
"@mastra/core": "1.28.0-alpha.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@mastra/core": ">=1.26.0-0 <2.0.0-0",
|