{"version":3,"sources":["../../src/browser/errors.ts","../../src/browser/processor.ts","../../src/browser/thread-manager.ts","../../src/browser/browser.ts","../../src/browser/screencast/types.ts","../../src/browser/screencast/screencast-stream.ts"],"names":["existsSync","readdirSync","join","lstatSync","unlinkSync","MastraBase","RegisteredLogger","isProcessorWorkflow","EventEmitter"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Unified error handling for browser tools.\n *\n * All browser tools return errors in this consistent format,\n * providing LLM-friendly messages and recovery hints.\n */\n\n/**\n * Error codes for browser tool failures.\n *\n * These codes help agents understand what went wrong\n * and whether retry or recovery is possible.\n */\nexport type ErrorCode =\n  | 'stale_ref' // Ref no longer valid after page change\n  | 'element_not_found' // Element doesn't exist\n  | 'element_blocked' // Element covered by overlay\n  | 'element_not_visible' // Element hidden\n  | 'not_focusable' // Can't type into element\n  | 'timeout' // Operation timed out\n  | 'browser_closed' // Browser was externally closed\n  | 'browser_error'; // Generic browser error\n\n/**\n * Structured error response for browser tool failures.\n *\n * Provides LLM-friendly error information with optional recovery hints.\n */\nexport interface BrowserToolError {\n  /** Always false for error responses */\n  success: false;\n  /** Error classification code */\n  code: ErrorCode;\n  /** LLM-friendly error description */\n  message: string;\n  /** Suggested recovery action (only when actionable) */\n  recoveryHint?: string;\n  /** Whether the operation can be retried */\n  canRetry: boolean;\n}\n\n/**\n * Error codes that are generally retryable.\n */\nconst RETRYABLE_CODES: Set<ErrorCode> = new Set(['timeout', 'element_blocked']);\n\n/**\n * Creates a structured error response for browser tools.\n *\n * Sets canRetry based on the error code: true for 'timeout' and 'element_blocked'.\n *\n * @param code - Error classification code\n * @param message - LLM-friendly error description\n * @param hint - Optional recovery hint (only when actionable)\n * @returns Typed BrowserToolError with canRetry set automatically\n */\nexport function createError(code: ErrorCode, message: string, hint?: string): BrowserToolError {\n  return {\n    success: false,\n    code,\n    message,\n    recoveryHint: hint,\n    canRetry: RETRYABLE_CODES.has(code),\n  };\n}\n","/**\n * BrowserContextProcessor\n *\n * Input processor that injects browser context into agent prompts.\n * Similar to ChatChannelProcessor for channels.\n *\n * - `processInput`: Adds a system message with stable context (provider, sessionId, headless mode).\n * - `processInputStep`: At step 0, prepends a `<system-reminder>` to the user's message\n *   with per-request data (current URL, page title).\n *\n * Reads from `requestContext.get('browser')`.\n *\n * @example\n * ```ts\n * const agent = new Agent({\n *   browser: new AgentBrowser({ ... }),\n *   inputProcessors: [new BrowserContextProcessor()],\n * });\n * ```\n */\n\nimport type {\n  ProcessInputArgs,\n  ProcessInputResult,\n  ProcessInputStepArgs,\n  ProcessInputStepResult,\n} from '../processors/index';\n\n/**\n * Browser context stored in RequestContext.\n * Set by the browser implementation or deployer.\n */\nexport interface BrowserContext {\n  /** Browser provider name (e.g., \"agent-browser\", \"stagehand\") */\n  provider: string;\n\n  /** Session ID for tracking */\n  sessionId?: string;\n\n  /** Whether browser is running in headless mode */\n  headless?: boolean;\n\n  /** Current page URL (updated per-request) */\n  currentUrl?: string;\n\n  /** Current page title (updated per-request) */\n  pageTitle?: string;\n\n  /** Whether browser is currently running */\n  isRunning?: boolean;\n}\n\n/**\n * Input processor that injects browser context into agent prompts.\n */\nexport class BrowserContextProcessor {\n  readonly id = 'browser-context';\n\n  processInput(args: ProcessInputArgs): ProcessInputResult {\n    const ctx = args.requestContext?.get('browser') as BrowserContext | undefined;\n    if (!ctx) return args.messageList;\n\n    const lines = [`You have access to a browser (${ctx.provider}).`];\n\n    if (ctx.headless === false) {\n      lines.push('The browser is running in visible mode (not headless).');\n    }\n\n    if (ctx.sessionId) {\n      lines.push(`Session ID: ${ctx.sessionId}`);\n    }\n\n    const systemMessages = [...args.systemMessages, { role: 'system' as const, content: lines.join(' ') }];\n\n    return { messages: args.messages, systemMessages };\n  }\n\n  processInputStep(args: ProcessInputStepArgs): ProcessInputStepResult | undefined {\n    // Only inject per-request context at the first step\n    if (args.stepNumber !== 0) return;\n\n    const ctx = args.requestContext?.get('browser') as BrowserContext | undefined;\n    if (!ctx) return;\n\n    const parts: string[] = [];\n\n    if (ctx.currentUrl) {\n      parts.push(`Current URL: ${ctx.currentUrl}`);\n    }\n\n    if (ctx.pageTitle) {\n      parts.push(`Page title: ${ctx.pageTitle}`);\n    }\n\n    if (ctx.isRunning === false) {\n      parts.push('Browser is not currently running.');\n    }\n\n    if (parts.length === 0) return;\n\n    const reminder = `<system-reminder>${parts.join(' | ')}</system-reminder>\\n\\n`;\n\n    // Prepend reminder to the last user message's text parts\n    const messages = [...args.messages];\n\n    for (let i = messages.length - 1; i >= 0; i--) {\n      const msg = messages[i]!;\n      if (msg.role === 'user') {\n        const content = msg.content;\n        // MastraMessageContentV2: { format: 2, parts: [...] }\n        const existingParts = content.parts ?? [];\n        const firstTextIdx = existingParts.findIndex((p: { type: string }) => p.type === 'text');\n\n        if (firstTextIdx >= 0) {\n          const textPart = existingParts[firstTextIdx] as { type: 'text'; text: string };\n          const newParts = [...existingParts];\n          newParts[firstTextIdx] = { ...textPart, text: reminder + textPart.text };\n          messages[i] = { ...msg, content: { ...content, parts: newParts } };\n        } else {\n          messages[i] = {\n            ...msg,\n            content: {\n              ...content,\n              parts: [{ type: 'text' as const, text: reminder }, ...existingParts],\n            },\n          };\n        }\n        break;\n      }\n    }\n\n    return { messages };\n  }\n}\n","/**\n * ThreadManager - Abstract base class for managing thread-scoped browser sessions.\n *\n * Similar to ProcessManager for workspaces, this centralizes thread lifecycle logic\n * and makes thread isolation reusable across browser providers.\n *\n * Browser scope modes:\n * - 'shared': All threads share a single browser instance\n * - 'thread': Each thread gets its own browser instance (full isolation)\n */\n\nimport type { IMastraLogger } from '../logger';\n\n/** Browser scope mode - determines how browser instances are shared across threads */\nexport type BrowserScope = 'shared' | 'thread';\n\n/** Default thread ID used when no thread is specified */\nexport const DEFAULT_THREAD_ID = '__default__';\n\n/**\n * Represents a single tab's state for persistence.\n */\nexport interface BrowserTabState {\n  url: string;\n  title?: string;\n}\n\n/**\n * Full browser state for persistence and restoration.\n */\nexport interface BrowserState {\n  tabs: BrowserTabState[];\n  activeTabIndex: number;\n}\n\n/**\n * Represents an active thread session.\n */\nexport interface ThreadSession {\n  /** Unique thread identifier */\n  threadId: string;\n  /** Timestamp when session was created */\n  createdAt: number;\n  /** Full browser state for this thread (for restore on relaunch) */\n  browserState?: BrowserState;\n}\n\n/**\n * Configuration for ThreadManager.\n */\nexport interface ThreadManagerConfig {\n  /** Browser scope mode */\n  scope: BrowserScope;\n  /** Logger instance */\n  logger?: IMastraLogger;\n  /** Callback when a new session is created */\n  onSessionCreated?: (session: ThreadSession) => void;\n  /** Callback when a session is destroyed */\n  onSessionDestroyed?: (threadId: string) => void;\n}\n\n/**\n * Abstract base class for managing thread-scoped browser sessions.\n *\n * @typeParam TManager - The browser manager type (e.g., BrowserManagerLike, Stagehand)\n */\nexport abstract class ThreadManager<TManager = unknown> {\n  protected readonly scope: BrowserScope;\n  protected readonly logger?: IMastraLogger;\n  protected readonly sessions = new Map<string, ThreadSession>();\n  protected activeThreadId: string = DEFAULT_THREAD_ID;\n\n  /** Preserved browser state that survives session clears (for browser restore) */\n  protected readonly savedBrowserStates = new Map<string, BrowserState>();\n\n  /** Shared manager instance (used for 'shared' scope) */\n  protected sharedManager: TManager | null = null;\n\n  /** Map of thread ID to dedicated manager instance (for 'thread' scope) */\n  protected readonly threadManagers = new Map<string, TManager>();\n\n  private readonly onSessionCreated?: (session: ThreadSession) => void;\n  private readonly onSessionDestroyed?: (threadId: string) => void;\n\n  constructor(config: ThreadManagerConfig) {\n    this.scope = config.scope;\n    this.logger = config.logger;\n    this.onSessionCreated = config.onSessionCreated;\n    this.onSessionDestroyed = config.onSessionDestroyed;\n  }\n\n  /**\n   * Get the current browser scope mode.\n   */\n  getScope(): BrowserScope {\n    return this.scope;\n  }\n\n  /**\n   * Get the currently active thread ID.\n   */\n  getActiveThreadId(): string {\n    return this.activeThreadId;\n  }\n\n  /**\n   * Set the shared manager instance (called after browser launch).\n   */\n  setSharedManager(manager: TManager): void {\n    this.sharedManager = manager;\n  }\n\n  /**\n   * Clear the shared manager instance (called when browser disconnects).\n   */\n  clearSharedManager(): void {\n    this.sharedManager = null;\n  }\n\n  /**\n   * Get the manager for an existing thread session without creating a new one.\n   *\n   * For 'thread' scope: Returns the thread-specific manager, or null if no session exists.\n   * For 'shared' scope: Returns the shared manager (all threads use the same instance).\n   *\n   * @param threadId - Thread identifier (defaults to DEFAULT_THREAD_ID)\n   * @returns The manager for the thread, or null if not found (thread scope only)\n   */\n  getExistingManagerForThread(threadId?: string): TManager | null {\n    const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n    if (this.scope === 'thread') {\n      return this.threadManagers.get(effectiveThreadId) ?? null;\n    }\n    return this.sharedManager;\n  }\n\n  /**\n   * Check if any thread managers are still running (for 'thread' scope).\n   */\n  hasActiveThreadManagers(): boolean {\n    return this.threadManagers.size > 0;\n  }\n\n  /**\n   * Clear all session tracking without closing managers.\n   * Used when browsers have been externally closed and we just need to reset state.\n   */\n  clearAllSessions(): void {\n    this.threadManagers.clear();\n    this.sessions.clear();\n    this.activeThreadId = DEFAULT_THREAD_ID;\n  }\n\n  /**\n   * Get a session by thread ID.\n   */\n  getSession(threadId: string): ThreadSession | undefined {\n    return this.sessions.get(threadId);\n  }\n\n  /**\n   * Check if a session exists for a thread.\n   */\n  hasSession(threadId: string): boolean {\n    return this.sessions.has(threadId);\n  }\n\n  /**\n   * List all active sessions.\n   */\n  listSessions(): ThreadSession[] {\n    return Array.from(this.sessions.values());\n  }\n\n  /**\n   * Get the number of active sessions.\n   */\n  getSessionCount(): number {\n    return this.sessions.size;\n  }\n\n  /**\n   * Get or create a session for a thread, and return the browser manager for that thread.\n   *\n   * For 'shared' scope, returns the shared manager.\n   * For 'thread' scope, creates/returns a dedicated manager for the thread.\n   *\n   * @param threadId - Thread identifier (uses DEFAULT_THREAD_ID if not provided)\n   * @returns The browser manager for the thread\n   */\n  async getManagerForThread(threadId?: string): Promise<TManager> {\n    const effectiveThreadId = threadId ?? DEFAULT_THREAD_ID;\n\n    // Shared scope - always use shared manager\n    // For thread scope, always create/use a dedicated session (even for DEFAULT_THREAD_ID)\n    if (this.scope === 'shared') {\n      return this.getSharedManager();\n    }\n\n    // Check if session already exists\n    let session = this.sessions.get(effectiveThreadId);\n\n    if (!session) {\n      // Create new session\n      session = await this.createSession(effectiveThreadId);\n      this.sessions.set(effectiveThreadId, session);\n      this.logger?.debug?.(`Created thread session: ${effectiveThreadId}`);\n      this.onSessionCreated?.(session);\n    }\n\n    this.activeThreadId = effectiveThreadId;\n    return this.getManagerForSession(session);\n  }\n\n  /**\n   * Destroy a specific thread's session.\n   *\n   * @param threadId - Thread identifier\n   */\n  async destroySession(threadId: string): Promise<void> {\n    const session = this.sessions.get(threadId);\n    if (!session) {\n      return;\n    }\n\n    await this.doDestroySession(session);\n    this.threadManagers.delete(threadId);\n    this.sessions.delete(threadId);\n    this.logger?.debug?.(`Destroyed thread session: ${threadId}`);\n    this.onSessionDestroyed?.(threadId);\n\n    // Reset active thread if we destroyed it\n    if (this.activeThreadId === threadId) {\n      this.activeThreadId = DEFAULT_THREAD_ID;\n    }\n  }\n\n  /**\n   * Destroy all thread sessions.\n   */\n  async destroyAllSessions(): Promise<void> {\n    const threadIds = Array.from(this.sessions.keys());\n    for (const threadId of threadIds) {\n      await this.destroySession(threadId);\n    }\n    this.activeThreadId = DEFAULT_THREAD_ID;\n  }\n\n  /**\n   * Update the browser state for a thread session.\n   * Also saves to persistent storage so state survives session clears.\n   */\n  updateBrowserState(threadId: string, state: BrowserState): void {\n    // Filter out empty/blank tabs\n    const filteredTabs = state.tabs.filter(tab => tab.url && tab.url !== 'about:blank');\n    if (filteredTabs.length === 0) {\n      return;\n    }\n\n    const filteredState: BrowserState = {\n      tabs: filteredTabs,\n      activeTabIndex: Math.max(0, Math.min(state.activeTabIndex, filteredTabs.length - 1)),\n    };\n\n    const session = this.sessions.get(threadId);\n    if (session) {\n      session.browserState = filteredState;\n    }\n    // Also save to persistent map so it survives session clears\n    this.savedBrowserStates.set(threadId, filteredState);\n  }\n\n  /**\n   * Get the saved browser state for a thread (survives session clears).\n   */\n  getSavedBrowserState(threadId: string): BrowserState | undefined {\n    // First check current session\n    const session = this.sessions.get(threadId);\n    if (session?.browserState) {\n      return session.browserState;\n    }\n    // Fall back to saved state\n    return this.savedBrowserStates.get(threadId);\n  }\n\n  /**\n   * Clear a specific thread's session without closing the browser.\n   * Used when a thread's browser has been externally closed.\n   * Preserves the browser state for potential restoration.\n   *\n   * @param threadId - The thread ID to clear\n   */\n  clearSession(threadId: string): void {\n    // Save the browser state before clearing so it can be restored on relaunch\n    const session = this.sessions.get(threadId);\n    if (session?.browserState) {\n      this.savedBrowserStates.set(threadId, session.browserState);\n    }\n    this.threadManagers.delete(threadId);\n    this.sessions.delete(threadId);\n    // Reset activeThreadId if we just cleared it\n    if (this.activeThreadId === threadId) {\n      this.activeThreadId = DEFAULT_THREAD_ID;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // Abstract methods to be implemented by subclasses\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Get the shared browser manager (used for 'shared' scope and default thread).\n   * @throws Error if shared manager is not initialized\n   */\n  protected getSharedManager(): TManager {\n    if (!this.sharedManager) {\n      throw new Error('Browser not launched');\n    }\n    return this.sharedManager;\n  }\n\n  /**\n   * Create a new session for a thread.\n   * Called when a thread is accessed for the first time.\n   */\n  protected abstract createSession(threadId: string): Promise<ThreadSession>;\n\n  /**\n   * Get the browser manager for a specific session.\n   */\n  protected abstract getManagerForSession(session: ThreadSession): TManager;\n\n  /**\n   * Destroy a session and clean up resources.\n   */\n  protected abstract doDestroySession(session: ThreadSession): Promise<void>;\n}\n","/**\n * MastraBrowser Base Class\n *\n * Abstract base class for browser providers. Extends MastraBase for logger integration.\n *\n * ## Architecture\n *\n * Each browser provider defines its own tools via the `getTools()` method.\n * This allows different providers to offer different capabilities:\n *\n * - **AgentBrowser**: 17 deterministic tools using refs ([ref=e1], [ref=e2])\n * - **StagehandBrowser**: AI-powered tools (act, extract, observe)\n *\n * ## Two Paradigms\n *\n * Browser providers fall into two paradigms:\n *\n * 1. **Deterministic** (Playwright, agent-browser) - Uses refs and selectors\n * 2. **AI-powered** (Stagehand) - Uses natural language instructions\n *\n * Both extend this base class and implement `getTools()` to return their tools.\n */\n\nimport { existsSync, unlinkSync, lstatSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { MastraBase } from '../base';\nimport { RegisteredLogger } from '../logger/constants';\nimport { isProcessorWorkflow } from '../processors/index';\nimport type { InputProcessor, InputProcessorOrWorkflow } from '../processors/index';\nimport type { Tool } from '../tools/tool';\nimport { createError } from './errors';\nimport type { BrowserToolError, ErrorCode } from './errors';\nimport { BrowserContextProcessor } from './processor';\nimport type { ScreencastOptions as ScreencastOptionsType } from './screencast/types';\nimport { DEFAULT_THREAD_ID } from './thread-manager';\nimport type { BrowserState, BrowserTabState, BrowserScope, ThreadManager } from './thread-manager';\n\n// Re-export screencast types from the screencast module\nexport type { ScreencastOptions, ScreencastFrameData, ScreencastEvents } from './screencast/types';\n\n// Alias for internal use\ntype ScreencastOptions = ScreencastOptionsType;\n\n// =============================================================================\n// Profile Lock File Cleanup\n// =============================================================================\n\n/**\n * Lock files that Chrome/Chromium creates in the profile directory.\n * These can become stale if the browser doesn't shut down cleanly.\n */\nconst CHROME_LOCK_FILES = ['SingletonLock', 'SingletonSocket', 'SingletonCookie', 'chrome.pid', 'RunningChromeVersion'];\n\n/**\n * Clean up stale Chrome lock files from a profile directory.\n *\n * Chrome creates lock files (SingletonLock, SingletonSocket, etc.) to prevent\n * multiple instances from using the same profile. If the browser crashes or\n * doesn't shut down cleanly, these files can remain and block future launches.\n *\n * This function removes these lock files, allowing the profile to be reused.\n * It's safe to call even if the files don't exist.\n *\n * @param profilePath - Path to the Chrome profile directory\n * @param logger - Optional logger for debug output\n */\nexport function cleanupProfileLockFiles(\n  profilePath: string,\n  logger?: { debug?: (message: string) => void; warn?: (message: string) => void },\n): void {\n  if (!profilePath || !existsSync(profilePath)) {\n    return;\n  }\n\n  try {\n    const entries = readdirSync(profilePath);\n    for (const entry of entries) {\n      if (CHROME_LOCK_FILES.includes(entry)) {\n        const fullPath = join(profilePath, entry);\n        try {\n          const stat = lstatSync(fullPath);\n          // Remove both regular files and symlinks\n          if (stat.isFile() || stat.isSymbolicLink()) {\n            unlinkSync(fullPath);\n            logger?.debug?.(`Removed stale lock file: ${fullPath}`);\n          }\n        } catch (err) {\n          // File may have been removed between readdir and unlink, ignore\n          logger?.warn?.(`Failed to remove lock file ${fullPath}: ${err}`);\n        }\n      }\n    }\n  } catch (err) {\n    // Profile directory may not be readable, ignore\n    logger?.warn?.(`Failed to clean up profile lock files in ${profilePath}: ${err}`);\n  }\n}\n\n// =============================================================================\n// Process Group Cleanup\n// =============================================================================\n\n/**\n * Kill a browser process and its children by sending SIGKILL to the process group.\n *\n * When Chrome/Chromium is launched, it spawns child processes (GPU, renderer,\n * network, storage, crashpad handlers). If the main process exits uncleanly,\n * these children can become orphaned. Killing the process group ensures all\n * related processes are cleaned up.\n *\n * Note: Process group signaling (`-pid`) is POSIX-only. On Windows, this\n * function is a no-op and orphaned child processes must be cleaned up by\n * other means (e.g., taskkill).\n *\n * @param pid - The PID of the main browser process. If undefined, this is a no-op.\n * @param logger - Optional logger for debug output.\n */\nexport function killProcessGroup(\n  pid: number | undefined,\n  logger?: { debug?: (message: string) => void; warn?: (message: string) => void },\n): void {\n  if (pid == null) return;\n  try {\n    process.kill(-pid, 'SIGKILL');\n    logger?.debug?.(`Killed process group for PID ${pid}`);\n  } catch (err) {\n    // ESRCH = process already gone — expected\n    const code = (err as NodeJS.ErrnoException).code;\n    if (code !== 'ESRCH') {\n      logger?.warn?.(`Failed to kill process group ${pid}: ${code ?? err}`);\n    }\n  }\n}\n\n// =============================================================================\n// Status & Lifecycle Types\n// =============================================================================\n\n/**\n * Browser provider status.\n */\nexport type BrowserStatus = 'pending' | 'launching' | 'ready' | 'error' | 'closing' | 'closed';\n\n/**\n * Lifecycle hook that fires during browser state transitions.\n */\nexport type BrowserLifecycleHook = (args: { browser: MastraBrowser }) => void | Promise<void>;\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/**\n * CDP URL provider - can be a static string or an async function.\n * Useful for cloud providers where the CDP URL may change per session.\n */\nexport type CdpUrlProvider = string | (() => string | Promise<string>);\n\n/**\n * Base configuration properties shared by all browser providers.\n * This interface contains fields common to all browser configurations.\n *\n * **For extending**: Use this interface when creating provider-specific configs\n * (e.g., `interface MyProviderConfig extends BrowserConfigBase`).\n *\n * **For consuming**: Use {@link BrowserConfig} which adds compile-time validation\n * that `cdpUrl` and `scope: 'thread'` cannot be used together.\n */\nexport interface BrowserConfigBase {\n  /**\n   * Whether to run the browser in headless mode (no visible UI).\n   * @default true\n   */\n  headless?: boolean;\n\n  /**\n   * Browser viewport dimensions.\n   * Controls the size of the browser window and how websites render.\n   */\n  viewport?: {\n    width: number;\n    height: number;\n  };\n\n  /**\n   * Default timeout in milliseconds for browser operations.\n   * @default 10000 (10 seconds)\n   */\n  timeout?: number;\n\n  /**\n   * CDP WebSocket URL or async provider function.\n   * When provided, connects to an existing browser instead of launching a new one.\n   * Useful for cloud providers (Browserbase, Browserless, Kernel, etc.).\n   *\n   * **Important:** When using `cdpUrl`, you must use `scope: 'shared'` (or omit `scope`\n   * to let it default to 'shared' behavior). Using `cdpUrl` with `scope: 'thread'`\n   * will throw an error because thread isolation requires spawning separate browser\n   * instances, which isn't possible when connecting to an existing browser via CDP.\n   *\n   * @example\n   * ```ts\n   * // Connect to a local Chrome with remote debugging enabled\n   * { cdpUrl: 'ws://localhost:9222' }\n   *\n   * // Connect to Browserless cloud provider\n   * { cdpUrl: 'wss://chrome.browserless.io?token=YOUR_TOKEN', scope: 'shared' }\n   *\n   * // Use an async provider function for dynamic URLs\n   * { cdpUrl: async () => await fetchBrowserlessUrl() }\n   * ```\n   */\n  cdpUrl?: CdpUrlProvider;\n\n  /**\n   * Browser instance scope across threads.\n   *\n   * - `'thread'` (default): Each thread gets its own isolated browser instance.\n   *   Best for parallel agents that need separate browser states.\n   *\n   * - `'shared'`: All threads share a single browser instance.\n   *   Required when using `cdpUrl` to connect to an existing browser.\n   *\n   * **Important:** `scope: 'thread'` cannot be used with `cdpUrl` because thread\n   * isolation requires spawning new browser instances, which isn't possible when\n   * connecting to an existing browser via CDP. This configuration will throw an error.\n   *\n   * @default 'thread'\n   *\n   * @example\n   * ```ts\n   * // Isolated browsers per thread (default)\n   * { scope: 'thread' }\n   *\n   * // Shared browser for all threads\n   * { scope: 'shared' }\n   *\n   * // When using cdpUrl, scope must be 'shared'\n   * { cdpUrl: 'ws://localhost:9222', scope: 'shared' }\n   * ```\n   */\n  scope?: BrowserScope;\n\n  /**\n   * Called after the browser reaches 'ready' status.\n   */\n  onLaunch?: BrowserLifecycleHook;\n\n  /**\n   * Called before the browser is closed.\n   */\n  onClose?: BrowserLifecycleHook;\n\n  /**\n   * Screencast options for streaming browser frames.\n   * Controls image format, quality, and dimensions.\n   */\n  screencast?: ScreencastOptions;\n\n  // ==========================================================================\n  // Profile & Authentication Options\n  // ==========================================================================\n\n  /**\n   * Path to a Chrome/Chromium user data directory (profile).\n   * When provided, the browser will use this profile's cookies, localStorage,\n   * extensions, and other session data.\n   *\n   * **Important:** Chrome only allows one process to access a profile at a time.\n   * If Chrome is already running with this profile, the browser will fail to launch.\n   * Either close Chrome first, or use a copy of the profile.\n   *\n   * @example\n   * ```ts\n   * // macOS Chrome default profile\n   * { profile: '/Users/you/Library/Application Support/Google/Chrome' }\n   *\n   * // Custom profile directory\n   * { profile: '/path/to/my-automation-profile' }\n   * ```\n   */\n  profile?: string;\n\n  /**\n   * Path to the browser executable to use.\n   * By default, Playwright/Stagehand use their bundled Chromium.\n   * Use this to launch a specific browser installation instead.\n   *\n   * @example\n   * ```ts\n   * // macOS Chrome\n   * { executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' }\n   *\n   * // Linux Chrome\n   * { executablePath: '/usr/bin/google-chrome' }\n   *\n   * // Windows Chrome\n   * { executablePath: 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe' }\n   * ```\n   */\n  executablePath?: string;\n}\n\n/**\n * Browser configuration with compile-time enforcement of cdpUrl/scope compatibility.\n *\n * This type enforces that `cdpUrl` and `scope: 'thread'` cannot be used together:\n * - When `cdpUrl` is provided, `scope` must be `'shared'` or omitted\n * - When `scope: 'thread'` is used, `cdpUrl` must not be provided\n *\n * @example\n * ```ts\n * // Valid configurations:\n * { headless: true }                              // Local browser, thread scope (default)\n * { scope: 'thread' }                             // Explicit thread isolation\n * { scope: 'shared' }                             // Shared browser\n * { cdpUrl: 'ws://localhost:9222' }               // CDP connection, defaults to shared\n * { cdpUrl: 'ws://localhost:9222', scope: 'shared' }  // CDP with explicit shared\n *\n * // Invalid configuration (TypeScript error):\n * { cdpUrl: 'ws://localhost:9222', scope: 'thread' }  // Error: cannot combine cdpUrl with thread scope\n * ```\n */\nexport type BrowserConfig =\n  | (BrowserConfigBase & { cdpUrl?: undefined; scope?: BrowserScope })\n  | (BrowserConfigBase & { cdpUrl: CdpUrlProvider; scope?: 'shared' });\n\n// =============================================================================\n// Screencast Types (re-exported from ./screencast/types)\n// =============================================================================\n\n/**\n * A screencast stream that emits frames.\n * Uses EventEmitter pattern for frame delivery.\n */\nexport interface ScreencastStream {\n  /** Stop the screencast */\n  stop(): Promise<void>;\n  /** Check if screencast is active */\n  isActive(): boolean;\n  /** Reconnect the screencast (e.g., after tab change) */\n  reconnect(): Promise<void>;\n  /** Register event handlers */\n  on(event: 'frame', handler: (frame: { data: string; viewport: { width: number; height: number } }) => void): this;\n  on(event: 'stop', handler: (reason: string) => void): this;\n  on(event: 'error', handler: (error: Error) => void): this;\n  on(event: 'url', handler: (url: string) => void): this;\n  /** Emit a URL update (called by browser providers on navigation) */\n  emitUrl(url: string): void;\n}\n\n// =============================================================================\n// Event Injection Types (for Studio live view)\n// =============================================================================\n\n/**\n * Mouse event parameters for CDP injection.\n */\nexport interface MouseEventParams {\n  type: 'mousePressed' | 'mouseReleased' | 'mouseMoved' | 'mouseWheel';\n  x: number;\n  y: number;\n  button?: 'left' | 'right' | 'middle' | 'none';\n  clickCount?: number;\n  deltaX?: number;\n  deltaY?: number;\n  modifiers?: number;\n}\n\n/**\n * Keyboard event parameters for CDP injection.\n */\nexport interface KeyboardEventParams {\n  type: 'keyDown' | 'keyUp' | 'char';\n  key?: string;\n  code?: string;\n  text?: string;\n  modifiers?: number;\n  /** Windows virtual key code (required for non-printable keys like Enter, Tab, Arrow keys) */\n  windowsVirtualKeyCode?: number;\n}\n\n// =============================================================================\n// MastraBrowser Base Class\n// =============================================================================\n\n/**\n * Abstract base class for browser providers.\n *\n * Providers extend this class and implement the abstract methods.\n * Each method corresponds to one of the 17 flat tools.\n */\nexport abstract class MastraBrowser extends MastraBase {\n  // ---------------------------------------------------------------------------\n  // Abstract Identity (providers must define)\n  // ---------------------------------------------------------------------------\n\n  /** Unique instance identifier */\n  abstract readonly id: string;\n\n  /** Human-readable name */\n  abstract readonly name: string;\n\n  /** Provider type (e.g., 'playwright', 'stagehand', 'browserbase') */\n  abstract readonly provider: string;\n\n  // ---------------------------------------------------------------------------\n  // State\n  // ---------------------------------------------------------------------------\n\n  /** Current lifecycle status */\n  status: BrowserStatus = 'pending';\n\n  /** Error message when status is 'error' */\n  error?: string;\n\n  /**\n   * Whether the browser is running in headless mode.\n   * Returns true by default if not explicitly configured.\n   */\n  get headless(): boolean {\n    return this.config.headless ?? true;\n  }\n\n  /** Last known browser state before browser was closed (for restore on relaunch) */\n  protected lastBrowserState?: BrowserState;\n\n  /**\n   * Shared manager instance for 'shared' scope mode.\n   * Type varies by provider (e.g., BrowserManager for agent-browser, Stagehand for stagehand).\n   * Providers should cast this to their specific type when accessing.\n   */\n  protected sharedManager: unknown = null;\n\n  /** Configuration */\n  protected readonly config: BrowserConfig;\n\n  /**\n   * Thread manager for handling thread-scoped browser sessions.\n   * Set by subclasses that support thread isolation.\n   */\n  protected threadManager?: ThreadManager;\n\n  /**\n   * Current thread ID for browser operations.\n   * Used by thread isolation to route operations to the correct session.\n   */\n  protected currentThreadId: string = DEFAULT_THREAD_ID;\n\n  // ---------------------------------------------------------------------------\n  // Screencast State\n  // ---------------------------------------------------------------------------\n\n  /** Default key for shared scope screencast streams */\n  protected static readonly SHARED_STREAM_KEY = '__shared__';\n\n  /** Active screencast streams per thread (for triggering reconnects on tab changes) */\n  protected activeScreencastStreams = new Map<string, ScreencastStream>();\n\n  // ---------------------------------------------------------------------------\n  // Process ID Tracking (for orphaned process cleanup)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * PID of the shared browser process.\n   * Set by providers after launch so the base class can kill the process group\n   * (GPU, renderer, crashpad, etc.) when the browser disconnects or closes.\n   */\n  protected sharedBrowserPid?: number;\n\n  /**\n   * PIDs of per-thread browser processes.\n   * Set by providers after creating a thread session.\n   */\n  protected threadBrowserPids = new Map<string, number>();\n\n  /**\n   * Get the stream key for a thread (or shared key for shared scope).\n   * @param threadId - Optional thread ID\n   * @returns The stream key to use for the screencast streams map\n   */\n  protected getStreamKey(threadId?: string): string {\n    return threadId || MastraBrowser.SHARED_STREAM_KEY;\n  }\n\n  /**\n   * Reconnect the active screencast for a specific thread.\n   * Called internally when tabs are switched or closed.\n   */\n  protected async reconnectScreencastForThread(threadId: string | undefined, reason: string): Promise<void> {\n    const streamKey = this.getStreamKey(threadId);\n    const stream = this.activeScreencastStreams.get(streamKey);\n    if (!stream || !stream.isActive()) {\n      return;\n    }\n\n    // Check if browser is still running before attempting reconnect\n    if (!this.isBrowserRunning()) {\n      this.logger.debug?.('Skipping screencast reconnect - browser not running');\n      return;\n    }\n\n    // For thread scope, also check if this specific thread still has a session\n    const scope = this.getScope();\n    if (scope === 'thread' && threadId && !this.threadManager?.getExistingManagerForThread(threadId)) {\n      this.logger.debug?.(`Skipping screencast reconnect - no session for thread ${threadId}`);\n      return;\n    }\n\n    this.logger.debug?.(`Reconnecting screencast: ${reason}`);\n\n    try {\n      // Small delay to let tab state settle\n      await new Promise(resolve => setTimeout(resolve, 150));\n      await stream.reconnect();\n\n      // Emit the URL of the new active page after reconnecting\n      const activePage = await this.getActivePage(threadId);\n      if (activePage) {\n        const url = activePage.url();\n        if (url) {\n          stream.emitUrl(url);\n        }\n      }\n    } catch (error) {\n      this.logger.debug?.('Screencast reconnect failed', error);\n    }\n  }\n\n  /**\n   * Update the browser state in the thread session.\n   * Called on navigation, tab open/close to keep state fresh.\n   */\n  protected updateSessionBrowserState(threadId?: string): void {\n    try {\n      const effectiveThreadId = threadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID;\n      const state = this.getBrowserStateForThread(effectiveThreadId);\n      if (state) {\n        this.threadManager?.updateBrowserState(effectiveThreadId, state);\n      }\n    } catch {\n      // Silently ignore errors during state update\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // Lifecycle Promise Tracking (prevents race conditions)\n  // ---------------------------------------------------------------------------\n\n  private _launchPromise?: Promise<void>;\n  private _closePromise?: Promise<void>;\n\n  // ---------------------------------------------------------------------------\n  // Constructor\n  // ---------------------------------------------------------------------------\n\n  constructor(config: BrowserConfig = {}) {\n    super({ name: 'MastraBrowser', component: RegisteredLogger.BROWSER });\n    this.config = config;\n\n    // Validate configuration: cdpUrl and scope: 'thread' are mutually exclusive\n    // When connecting to an external browser via cdpUrl, we connect to a single existing browser.\n    // Thread isolation requires spawning separate browser instances, which isn't possible with cdpUrl.\n    // Note: The BrowserConfig type enforces this at compile-time, but we keep this runtime check\n    // for better error messages when users bypass TypeScript (e.g., from JavaScript or casting).\n    // We capture scope before checking cdpUrl to avoid TypeScript narrowing the union type.\n    const scope = config.scope;\n    if (config.cdpUrl && scope === 'thread') {\n      throw new Error(\n        'Invalid browser configuration: \"cdpUrl\" and \"scope: \\'thread\\'\" cannot be used together.\\n\\n' +\n          '• cdpUrl connects to a single existing browser instance (all threads share it)\\n' +\n          '• scope: \"thread\" requires spawning separate browser instances per thread\\n\\n' +\n          'To fix this, either:\\n' +\n          '1. Remove cdpUrl to let the provider spawn separate browser instances (supports thread isolation)\\n' +\n          '2. Use scope: \"shared\" when connecting via cdpUrl (all threads share one browser)',\n      );\n    }\n\n    // Validate: cdpUrl is incompatible with launch-time options (profile, executablePath).\n    // CDP connects to an already-running browser — it has its own profile and executable.\n    if (config.cdpUrl && (config.profile || config.executablePath)) {\n      const conflicting = [config.profile && 'profile', config.executablePath && 'executablePath']\n        .filter(Boolean)\n        .join(' and ');\n      throw new Error(\n        `Invalid browser configuration: \"cdpUrl\" cannot be used with ${conflicting}.\\n\\n` +\n          '• cdpUrl connects to an existing browser (which has its own profile and executable)\\n' +\n          '• profile and executablePath are launch-time options for spawning a new browser\\n\\n' +\n          'To fix this, either:\\n' +\n          '1. Remove cdpUrl to launch a new browser with your profile/executable\\n' +\n          '2. Remove profile/executablePath to connect to the existing browser via CDP',\n      );\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // Lifecycle Management\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Launch the browser. Override in subclass.\n   * Called by launch() wrapper which handles status and race conditions.\n   */\n  protected abstract doLaunch(): Promise<void>;\n\n  /**\n   * Close the browser. Override in subclass.\n   * Called by close() wrapper which handles status and race conditions.\n   */\n  protected abstract doClose(): Promise<void>;\n\n  /**\n   * Launch the browser.\n   * Race-condition-safe - handles concurrent calls, status management, and lifecycle hooks.\n   */\n  async launch(): Promise<void> {\n    // Already ready\n    if (this.status === 'ready') {\n      return;\n    }\n\n    // Already launching - wait for existing promise\n    if (this.status === 'launching' && this._launchPromise) {\n      return this._launchPromise;\n    }\n\n    // Can't launch if closing/closed\n    if (this.status === 'closing' || this.status === 'closed') {\n      throw new Error(`Cannot launch browser in '${this.status}' state`);\n    }\n\n    this.status = 'launching';\n    this.error = undefined;\n\n    this._launchPromise = (async () => {\n      try {\n        await this.doLaunch();\n        this.status = 'ready';\n\n        // Fire onLaunch hook\n        if (this.config.onLaunch) {\n          await this.config.onLaunch({ browser: this });\n        }\n\n        // Notify onBrowserReady callbacks\n        this.notifyBrowserReady();\n      } catch (err) {\n        this.status = 'error';\n        this.error = err instanceof Error ? err.message : String(err);\n        throw err;\n      } finally {\n        this._launchPromise = undefined;\n      }\n    })();\n\n    return this._launchPromise;\n  }\n\n  /**\n   * Close the browser.\n   * Race-condition-safe - handles concurrent calls, status management, and lifecycle hooks.\n   */\n  async close(): Promise<void> {\n    // Already closed\n    if (this.status === 'closed') {\n      return;\n    }\n\n    // Already closing - wait for existing promise\n    if (this.status === 'closing' && this._closePromise) {\n      return this._closePromise;\n    }\n\n    // Wait for in-flight launch to complete before closing\n    // This prevents race conditions where close() executes against a half-initialized provider\n    if (this.status === 'launching' && this._launchPromise) {\n      try {\n        await this._launchPromise;\n      } catch {\n        // Launch failed - status is now 'error', nothing to close\n        // Ensure we're in a clean closed state and return early\n        this.status = 'closed';\n        return;\n      }\n    }\n\n    // Fire onClose hook before closing\n    if (this.config.onClose && this.status === 'ready') {\n      await this.config.onClose({ browser: this });\n    }\n\n    // Save browser state before closing for potential restore on relaunch\n    const currentState = await this.getBrowserState();\n    if (currentState && currentState.tabs.length > 0) {\n      this.lastBrowserState = currentState;\n    }\n\n    this.status = 'closing';\n\n    this._closePromise = (async () => {\n      try {\n        await this.doClose();\n        this.status = 'closed';\n        this.notifyBrowserClosed();\n        // Clean up stale lock files only after confirmed shutdown.\n        // Removing them from a live profile (if doClose threw) could cause corruption.\n        if (this.config.profile) {\n          cleanupProfileLockFiles(this.config.profile, this.logger);\n        }\n      } catch (err) {\n        this.status = 'error';\n        this.error = err instanceof Error ? err.message : String(err);\n        throw err;\n      } finally {\n        this._closePromise = undefined;\n        // Kill orphaned child processes (GPU, renderer, crashpad, etc.)\n        killProcessGroup(this.sharedBrowserPid, this.logger);\n        this.sharedBrowserPid = undefined;\n        for (const [, pid] of this.threadBrowserPids) {\n          killProcessGroup(pid, this.logger);\n        }\n        this.threadBrowserPids.clear();\n      }\n    })();\n\n    return this._closePromise;\n  }\n\n  /**\n   * Ensure the browser is ready, launching if needed.\n   * If browser was previously closed, it will be re-launched.\n   */\n  async ensureReady(): Promise<void> {\n    if (this.status === 'ready') {\n      // Check if browser is still alive (handles external closure)\n      // checkBrowserAlive() should save lastBrowserState internally if it detects closure\n      const stillAlive = await this.checkBrowserAlive();\n      if (stillAlive) {\n        return;\n      }\n      // Browser was externally closed, mark as closed for re-launch\n      this.status = 'closed';\n    }\n    if (this.status === 'pending' || this.status === 'error' || this.status === 'closed') {\n      // Reset to pending to allow re-launch after close\n      if (this.status === 'closed') {\n        this.status = 'pending';\n      }\n      await this.launch();\n      return;\n    }\n    if (this.status === 'launching') {\n      await this._launchPromise;\n      return;\n    }\n    if (this.status === 'closing') {\n      // Wait for close to complete, then re-launch\n      await this._closePromise;\n      this.status = 'pending';\n      await this.launch();\n      return;\n    }\n    throw new Error(`Browser is ${this.status} and cannot be used`);\n  }\n\n  /**\n   * Check if the browser is still alive.\n   * Override in subclass to detect externally closed browsers.\n   * @returns true if browser is alive, false if it was externally closed\n   */\n  protected async checkBrowserAlive(): Promise<boolean> {\n    // Default implementation assumes browser is alive if status is ready\n    return true;\n  }\n\n  /**\n   * Check if the browser is currently running.\n   */\n  isBrowserRunning(): boolean {\n    return this.status === 'ready';\n  }\n\n  // ---------------------------------------------------------------------------\n  // CDP URL Resolution\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Resolve a CDP URL from a static string or async provider function.\n   * @param cdpUrl - Static string or async function returning the CDP URL\n   * @returns Resolved CDP URL string\n   */\n  protected async resolveCdpUrl(cdpUrl: CdpUrlProvider): Promise<string> {\n    return typeof cdpUrl === 'function' ? await cdpUrl() : cdpUrl;\n  }\n\n  /**\n   * Resolve an HTTP CDP endpoint to a WebSocket URL by fetching /json/version.\n   *\n   * Cloud browser providers (Browser-Use, Browserless, etc.) often expose HTTP\n   * endpoints that need to be resolved to WebSocket URLs for direct CDP connections.\n   *\n   * - If the URL starts with `ws://` or `wss://`, returns it as-is\n   * - If the URL starts with `http://` or `https://`, fetches /json/version to get webSocketDebuggerUrl\n   *\n   * @param url - CDP URL (HTTP or WebSocket)\n   * @returns WebSocket URL for CDP connection\n   */\n  protected async resolveWebSocketUrl(url: string): Promise<string> {\n    // Already a WebSocket URL\n    if (url.startsWith('ws://') || url.startsWith('wss://')) {\n      return url;\n    }\n\n    // HTTP URL - fetch /json/version to get the WebSocket URL\n    if (url.startsWith('http://') || url.startsWith('https://')) {\n      const baseUrl = url.replace(/\\/$/, ''); // Remove trailing slash\n      const versionUrl = `${baseUrl}/json/version`;\n\n      this.logger.debug?.(`Resolving WebSocket URL from ${versionUrl}`);\n\n      // Add timeout to prevent hanging on dead endpoints\n      const controller = new AbortController();\n      const timeoutId = setTimeout(() => controller.abort(), 10000);\n\n      try {\n        const response = await fetch(versionUrl, { signal: controller.signal });\n        clearTimeout(timeoutId);\n\n        if (!response.ok) {\n          throw new Error(\n            `Failed to fetch CDP version info from ${versionUrl}: ${response.status} ${response.statusText}`,\n          );\n        }\n\n        const data = (await response.json()) as { webSocketDebuggerUrl?: string };\n        if (!data.webSocketDebuggerUrl) {\n          throw new Error(`No webSocketDebuggerUrl found in CDP version response from ${versionUrl}`);\n        }\n\n        this.logger.debug?.(`Resolved WebSocket URL: ${data.webSocketDebuggerUrl}`);\n        return data.webSocketDebuggerUrl;\n      } catch (error) {\n        clearTimeout(timeoutId);\n        if (error instanceof Error && error.name === 'AbortError') {\n          throw new Error(`Timeout resolving WebSocket URL from ${versionUrl} (10s)`);\n        }\n        throw error;\n      }\n    }\n\n    // Unknown protocol - return as-is and let the caller handle it\n    return url;\n  }\n\n  // ---------------------------------------------------------------------------\n  // Disconnection Detection & Error Handling\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Error patterns that indicate browser disconnection.\n   * Used by isDisconnectionError() to detect external browser closure.\n   */\n  protected static readonly DISCONNECTION_PATTERNS = [\n    'Target closed',\n    'Target page, context or browser has been closed',\n    'Browser has been closed',\n    'Connection closed',\n    'Protocol error',\n    'Session closed',\n    'browser has disconnected',\n    'closed externally',\n  ];\n\n  /**\n   * Check if an error message indicates browser disconnection.\n   * @param message - Error message to check\n   * @returns true if the message indicates disconnection\n   */\n  isDisconnectionError(message: string): boolean {\n    const lowerMessage = message.toLowerCase();\n    return MastraBrowser.DISCONNECTION_PATTERNS.some(pattern => lowerMessage.includes(pattern.toLowerCase()));\n  }\n\n  /**\n   * Handle browser disconnection by updating status and notifying listeners.\n   * Called when browser is detected as externally closed.\n   *\n   * For 'thread' scope: clears only the specific thread's session (other threads unaffected)\n   * For 'shared' scope: clears the shared manager and updates global status\n   */\n  handleBrowserDisconnected(): void {\n    const scope = this.threadManager?.getScope();\n    const threadId = this.getCurrentThread();\n\n    if (scope === 'thread' && threadId !== DEFAULT_THREAD_ID) {\n      // Kill orphaned child processes for this thread\n      const pid = this.threadBrowserPids.get(threadId);\n      killProcessGroup(pid, this.logger);\n      this.threadBrowserPids.delete(threadId);\n      // Only clear the specific thread's session - other threads have independent browsers\n      this.threadManager!.clearSession(threadId);\n      this.logger.debug?.(`Cleared browser session for thread: ${threadId}`);\n      // Notify only this thread's callbacks - do NOT set global status to 'closed'\n      // since other threads may still have active browsers\n      this.notifyBrowserClosed(threadId);\n    } else {\n      // Kill orphaned child processes for the shared browser\n      killProcessGroup(this.sharedBrowserPid, this.logger);\n      this.sharedBrowserPid = undefined;\n      // For 'shared' scope or default thread, the shared browser is gone\n      this.sharedManager = null;\n      // Also clear the shared manager in the thread manager so getManagerForThread\n      // doesn't return the dead manager\n      this.threadManager?.clearSharedManager();\n      // Update global status and notify all callbacks\n      if (this.status !== 'closed') {\n        this.status = 'closed';\n        this.logger.debug?.('Browser was externally closed, status set to closed');\n        this.notifyBrowserClosed();\n      }\n    }\n\n    // Clean up stale lock files in the profile directory\n    // This is especially important for external/manual close which may leave locks behind\n    if (this.config.profile) {\n      cleanupProfileLockFiles(this.config.profile, this.logger);\n    }\n  }\n\n  /**\n   * Create a BrowserToolError from an exception.\n   * Handles common error patterns including disconnection detection.\n   * Subclasses can override to add provider-specific error handling.\n   *\n   * @param error - The caught error\n   * @param context - Description of what operation failed (e.g., \"Click operation\")\n   * @returns Structured BrowserToolError\n   */\n  protected createErrorFromException(error: unknown, context: string): BrowserToolError {\n    const msg = error instanceof Error ? error.message : String(error);\n\n    // Check for browser disconnection errors first\n    if (this.isDisconnectionError(msg)) {\n      this.handleBrowserDisconnected();\n      return createError(\n        'browser_closed',\n        'Browser was closed externally.',\n        'The browser window was closed. Please retry to re-launch.',\n      );\n    }\n\n    // Timeout errors\n    if (msg.includes('timeout') || msg.includes('Timeout') || msg.includes('aborted')) {\n      return createError('timeout', `${context} timed out.`, 'Try again or increase timeout.');\n    }\n\n    // Not launched errors\n    if (msg.includes('not launched') || msg.includes('Browser is not launched')) {\n      return createError(\n        'browser_error',\n        'Browser was not initialized.',\n        'This is an internal error - please try again.',\n      );\n    }\n\n    // Default to generic browser error\n    return createError('browser_error', `${context} failed: ${msg}`, 'Check the browser state and try again.');\n  }\n\n  /**\n   * Create a specific error type.\n   * Convenience method for providers to create typed errors.\n   */\n  protected createError(code: ErrorCode, message: string, hint?: string): BrowserToolError {\n    return createError(code, message, hint);\n  }\n\n  // ---------------------------------------------------------------------------\n  // Browser Ready/Closed Callbacks\n  // ---------------------------------------------------------------------------\n\n  private _onReadyCallbacks: Set<() => void> = new Set();\n  private _onClosedCallbacks: Set<() => void> = new Set();\n  /** Thread-specific ready callbacks. Key is threadId. */\n  private _onThreadReadyCallbacks: Map<string, Set<() => void>> = new Map();\n  /** Thread-specific closed callbacks. Key is threadId. */\n  private _onThreadClosedCallbacks: Map<string, Set<() => void>> = new Map();\n\n  /**\n   * Register a callback to be invoked when the browser becomes ready.\n   * If browser is already running, callback is invoked immediately.\n   * The callback is ALWAYS registered (even if invoked immediately) so it will\n   * also fire on future \"ready\" events (e.g., session creation for thread isolation).\n   * @param callback - Function to call when browser is ready\n   * @param threadId - Optional thread ID to scope the callback to a specific thread\n   * @returns Cleanup function to unregister the callback\n   */\n  onBrowserReady(callback: () => void, threadId?: string): () => void {\n    if (threadId) {\n      // Thread-specific callback\n      let threadCallbacks = this._onThreadReadyCallbacks.get(threadId);\n      if (!threadCallbacks) {\n        threadCallbacks = new Set();\n        this._onThreadReadyCallbacks.set(threadId, threadCallbacks);\n      }\n      threadCallbacks.add(callback);\n\n      // Check if this specific thread has a session ready\n      if (this.hasThreadSession(threadId)) {\n        callback();\n      }\n\n      return () => {\n        threadCallbacks!.delete(callback);\n        if (threadCallbacks!.size === 0) {\n          this._onThreadReadyCallbacks.delete(threadId);\n        }\n      };\n    }\n\n    // Global callback (for shared scope or when thread not specified)\n    this._onReadyCallbacks.add(callback);\n\n    if (this.isBrowserRunning()) {\n      // Browser already ready - also invoke immediately\n      callback();\n    }\n\n    return () => {\n      this._onReadyCallbacks.delete(callback);\n    };\n  }\n\n  /**\n   * Register a callback to be invoked when the browser closes.\n   * Useful for screencast to broadcast browser_closed status.\n   * @param callback - Function to call when browser closes\n   * @param threadId - Optional thread ID to scope the callback to a specific thread\n   * @returns Cleanup function to unregister the callback\n   */\n  onBrowserClosed(callback: () => void, threadId?: string): () => void {\n    if (threadId) {\n      // Thread-specific callback\n      let threadCallbacks = this._onThreadClosedCallbacks.get(threadId);\n      if (!threadCallbacks) {\n        threadCallbacks = new Set();\n        this._onThreadClosedCallbacks.set(threadId, threadCallbacks);\n      }\n      threadCallbacks.add(callback);\n      return () => {\n        threadCallbacks!.delete(callback);\n        if (threadCallbacks!.size === 0) {\n          this._onThreadClosedCallbacks.delete(threadId);\n        }\n      };\n    }\n    // Global callback (for shared scope or when thread not specified)\n    this._onClosedCallbacks.add(callback);\n    return () => {\n      this._onClosedCallbacks.delete(callback);\n    };\n  }\n\n  /**\n   * Notify registered callbacks that browser is ready.\n   * @param threadId - If provided, only notify callbacks for that thread (for thread scope)\n   */\n  protected notifyBrowserReady(threadId?: string): void {\n    if (threadId) {\n      // Notify thread-specific callbacks only\n      const threadCallbacks = this._onThreadReadyCallbacks.get(threadId);\n      if (threadCallbacks) {\n        for (const callback of threadCallbacks) {\n          try {\n            callback();\n          } catch {\n            // Intentionally swallowed - callbacks should not crash the browser\n          }\n        }\n      }\n    } else {\n      // Notify global callbacks (for shared scope)\n      for (const callback of this._onReadyCallbacks) {\n        try {\n          callback();\n        } catch {\n          // Intentionally swallowed - callbacks should not crash the browser\n        }\n      }\n      // Also notify ALL thread callbacks (entire browser is ready - shared scenario)\n      for (const [, threadCallbacks] of this._onThreadReadyCallbacks) {\n        for (const callback of threadCallbacks) {\n          try {\n            callback();\n          } catch {\n            // Intentionally swallowed - callbacks should not crash the browser\n          }\n        }\n      }\n    }\n    // Do NOT clear callbacks - they should persist across browser restarts\n    // so screencast can reconnect after external closure + re-launch\n  }\n\n  /**\n   * Notify registered callbacks that browser has closed.\n   * @param threadId - If provided, only notify callbacks for that thread (for thread scope)\n   */\n  protected notifyBrowserClosed(threadId?: string): void {\n    if (threadId) {\n      // Notify thread-specific callbacks only\n      const threadCallbacks = this._onThreadClosedCallbacks.get(threadId);\n      if (threadCallbacks) {\n        for (const callback of threadCallbacks) {\n          try {\n            callback();\n          } catch {\n            // Intentionally swallowed - callbacks should not crash the browser\n          }\n        }\n      }\n    } else {\n      // Notify global callbacks (for shared scope)\n      for (const callback of this._onClosedCallbacks) {\n        try {\n          callback();\n        } catch {\n          // Intentionally swallowed - callbacks should not crash the browser\n        }\n      }\n      // Also notify ALL thread callbacks (entire browser is closing)\n      for (const [, threadCallbacks] of this._onThreadClosedCallbacks) {\n        for (const callback of threadCallbacks) {\n          try {\n            callback();\n          } catch {\n            // Intentionally swallowed - callbacks should not crash the browser\n          }\n        }\n      }\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // URL Access (optional - providers that support it should override)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Get the current page URL without launching the browser.\n   * @param threadId - Optional thread ID for thread-isolated browsers\n   * @returns The current URL string, or null if browser is not running or not supported\n   */\n  async getCurrentUrl(_threadId?: string): Promise<string | null> {\n    return null;\n  }\n\n  /**\n   * Get the current browser state (all tabs and active tab index).\n   * Override in subclass to provide actual tab state.\n   * @param _threadId - Optional thread ID for thread-isolated sessions\n   * @returns The browser state, or null if not available\n   */\n  async getBrowserState(_threadId?: string): Promise<BrowserState | null> {\n    // Default implementation returns null - providers override\n    return null;\n  }\n\n  /**\n   * Get the last known browser state before the browser was closed.\n   * Useful for restoring state on relaunch.\n   * @param threadId - Optional thread ID for thread-isolated sessions\n   * @returns The last browser state, or undefined if not available\n   */\n  getLastBrowserState(threadId?: string): BrowserState | undefined {\n    // For thread isolation, check thread manager first\n    if (threadId && this.threadManager) {\n      const savedState = this.threadManager.getSavedBrowserState(threadId);\n      if (savedState) {\n        return savedState;\n      }\n    }\n    return this.lastBrowserState;\n  }\n\n  /**\n   * Get all open tabs with their URLs and titles.\n   * Override in subclass to provide actual tab info.\n   * @param _threadId - Optional thread ID for thread-isolated sessions\n   * @returns Array of tab states\n   */\n  async getTabState(_threadId?: string): Promise<BrowserTabState[]> {\n    // Default implementation returns empty array - providers override\n    return [];\n  }\n\n  /**\n   * Get the active tab index.\n   * Override in subclass to provide actual active tab index.\n   * @param _threadId - Optional thread ID for thread-isolated sessions\n   * @returns The active tab index (0-based), or 0 if not available\n   */\n  async getActiveTabIndex(_threadId?: string): Promise<number> {\n    // Default implementation returns 0 - providers override\n    return 0;\n  }\n\n  /**\n   * Navigate to a URL (simple form). Override in subclass if supported.\n   * Used internally for restoring state on relaunch.\n   * Named `navigateTo` to avoid conflicts with tool methods that have richer signatures.\n   */\n  async navigateTo(_url: string): Promise<void> {\n    // Default implementation does nothing - providers can override\n  }\n\n  // ---------------------------------------------------------------------------\n  // Thread Management\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Set the current thread ID for subsequent browser operations.\n   * Called by tools before executing browser actions to ensure\n   * operations are routed to the correct thread session.\n   *\n   * @param threadId - The thread ID, or undefined to use the default thread\n   */\n  setCurrentThread(threadId?: string): void {\n    this.currentThreadId = threadId ?? DEFAULT_THREAD_ID;\n  }\n\n  /**\n   * Get the current thread ID.\n   * @returns The current thread ID being used for operations\n   */\n  getCurrentThread(): string {\n    return this.currentThreadId;\n  }\n\n  /**\n   * Get the browser scope mode.\n   * @returns The scope from threadManager or config, defaults to 'shared'\n   */\n  getScope(): BrowserScope {\n    return this.threadManager?.getScope() ?? this.config.scope ?? 'shared';\n  }\n\n  // ---------------------------------------------------------------------------\n  // Screencast (optional - for Studio live view)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Start screencast streaming. Override in subclass if supported.\n   */\n  async startScreencast(_options?: ScreencastOptions): Promise<ScreencastStream> {\n    throw new Error('Screencast not supported by this provider');\n  }\n\n  /**\n   * Check if a thread has an existing browser session.\n   * Used by startScreencastIfBrowserActive to prevent showing another thread's page.\n   *\n   * If threadManager is set, delegates to it. Otherwise returns true (no isolation).\n   * Subclasses can override for custom behavior.\n   *\n   * @returns true if session exists or thread isolation is not used\n   */\n  hasThreadSession(threadId: string): boolean {\n    if (!this.threadManager) {\n      // No thread manager - all threads share the same session\n      return true;\n    }\n\n    const scope = this.threadManager.getScope();\n\n    // Shared scope - all threads share the same session\n    if (scope === 'shared') {\n      return true;\n    }\n\n    // Check if this thread has an actual session\n    return this.threadManager.hasSession(threadId);\n  }\n\n  /**\n   * Close a specific thread's browser session.\n   * Delegates to ThreadManager and notifies registered callbacks.\n   *\n   * For 'thread' scope, this closes only that thread's browser instance.\n   * For 'shared' scope, this is a no-op (use close() to close the shared browser).\n   *\n   * @param threadId - The thread ID whose session should be closed\n   */\n  async closeThreadSession(threadId: string): Promise<void> {\n    if (!this.threadManager) {\n      return;\n    }\n    await this.threadManager.destroySession(threadId);\n    // Kill orphaned child processes for this thread\n    const pid = this.threadBrowserPids.get(threadId);\n    killProcessGroup(pid, this.logger);\n    this.threadBrowserPids.delete(threadId);\n    // Notify callbacks registered for this specific thread\n    this.notifyBrowserClosed(threadId);\n    // Clean up any stale lock files in the profile directory\n    if (this.config.profile) {\n      cleanupProfileLockFiles(this.config.profile, this.logger);\n    }\n  }\n\n  /**\n   * Handle browser disconnection for a specific thread.\n   * Called when a thread's browser is closed externally (e.g., user closes browser window).\n   * Clears the thread session and notifies registered callbacks.\n   *\n   * @param threadId - The thread ID whose session was disconnected\n   */\n  protected handleThreadBrowserDisconnected(threadId: string): void {\n    if (!this.threadManager) {\n      return;\n    }\n    // Kill orphaned child processes for this thread\n    const pid = this.threadBrowserPids.get(threadId);\n    killProcessGroup(pid, this.logger);\n    this.threadBrowserPids.delete(threadId);\n\n    this.threadManager.clearSession(threadId);\n    this.logger.debug?.(`Cleared browser session for thread: ${threadId}`);\n    // Notify only the callbacks registered for this specific thread\n    this.notifyBrowserClosed(threadId);\n    // Clean up any stale lock files in the profile directory\n    if (this.config.profile) {\n      cleanupProfileLockFiles(this.config.profile, this.logger);\n    }\n  }\n\n  /**\n   * Get a session identifier for a specific thread.\n   * In thread scope, returns a composite ID (browser:threadId).\n   * In shared scope or without thread manager, returns the browser instance ID.\n   */\n  getSessionId(threadId?: string): string {\n    if (!threadId || !this.threadManager) {\n      return this.id;\n    }\n\n    const scope = this.threadManager.getScope();\n\n    // Shared scope - all threads share the same session\n    if (scope === 'shared') {\n      return this.id;\n    }\n\n    // Thread scope - return composite ID\n    return `${this.id}:${threadId}`;\n  }\n\n  /**\n   * Start screencast only if browser is already running.\n   * Does NOT launch the browser.\n   * Uses config.screencast options as defaults if no options provided.\n   *\n   * For thread-isolated browsers ('browser' mode):\n   * - Returns null if the thread doesn't have an existing browser session\n   */\n  async startScreencastIfBrowserActive(options?: ScreencastOptions): Promise<ScreencastStream | null> {\n    if (!this.isBrowserRunning()) {\n      return null;\n    }\n\n    // Merge config screencast defaults with call-site overrides\n    const mergedOptions = this.config.screencast || options ? { ...this.config.screencast, ...options } : undefined;\n\n    const threadId = mergedOptions?.threadId;\n    const scope = this.threadManager?.getScope() ?? this.config.scope ?? 'shared';\n\n    // Shared scope - just start the screencast\n    if (scope === 'shared') {\n      return this.startScreencast(mergedOptions);\n    }\n\n    // For 'thread' scope, only start if the thread has an existing session\n    if (threadId && !this.hasThreadSession(threadId)) {\n      return null;\n    }\n\n    return this.startScreencast(mergedOptions);\n  }\n\n  // ---------------------------------------------------------------------------\n  // Event Injection (optional - for Studio live view)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Inject a mouse event. Override in subclass if supported.\n   * @param event - Mouse event parameters\n   * @param threadId - Optional thread ID for thread-isolated sessions\n   */\n  async injectMouseEvent(_event: MouseEventParams, _threadId?: string): Promise<void> {\n    throw new Error('Mouse event injection not supported by this provider');\n  }\n\n  /**\n   * Inject a keyboard event. Override in subclass if supported.\n   * @param event - Keyboard event parameters\n   * @param threadId - Optional thread ID for thread-isolated sessions\n   */\n  async injectKeyboardEvent(_event: KeyboardEventParams, _threadId?: string): Promise<void> {\n    throw new Error('Keyboard event injection not supported by this provider');\n  }\n\n  // ---------------------------------------------------------------------------\n  // Abstract Methods (providers must implement)\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Get the active page for a thread.\n   * Used by screencast reconnection to emit the current URL.\n   *\n   * @param threadId - Optional thread ID (uses current thread if not provided)\n   * @returns The active Playwright Page, or null if not available\n   */\n  protected abstract getActivePage(threadId?: string): Promise<{ url(): string } | null>;\n\n  /**\n   * Get the current browser state for a thread.\n   * Used to persist and restore browser state across sessions.\n   *\n   * @param threadId - Optional thread ID (uses current thread if not provided)\n   * @returns Browser state including URL, tabs, and active tab index\n   */\n  protected abstract getBrowserStateForThread(threadId?: string): BrowserState | null;\n\n  // ---------------------------------------------------------------------------\n  // Input Processors\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Returns browser input processors (e.g., BrowserContextProcessor for context injection).\n   * Skips if the user already added a processor with the same id.\n   *\n   * This method is similar to AgentChannels.getInputProcessors() and allows\n   * browser implementations to provide their own processors.\n   *\n   * @param configuredProcessors - Processors already configured by the user (for deduplication)\n   * @returns Array of input processors for this browser instance\n   */\n  getInputProcessors(configuredProcessors: InputProcessorOrWorkflow[] = []): InputProcessor[] {\n    const hasProcessor = configuredProcessors.some(\n      p => !isProcessorWorkflow(p) && 'id' in p && p.id === 'browser-context',\n    );\n    if (hasProcessor) return [];\n    return [new BrowserContextProcessor()];\n  }\n\n  // ---------------------------------------------------------------------------\n  // Abstract Methods - Must be implemented by providers\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Get the browser tools for this provider.\n   *\n   * Each provider returns its own set of tools. For example:\n   * - AgentBrowser returns 17 deterministic tools using refs\n   * - StagehandBrowser might return AI-powered tools (act, extract, observe)\n   *\n   * @returns Record of tool name to tool definition\n   */\n  abstract getTools(): Record<string, Tool<any, any>>;\n}\n","/**\n * Screencast types for CDP-based browser streaming.\n *\n * These types are shared between browser providers (AgentBrowser, Stagehand, etc.)\n * that support screencast via CDP.\n */\n\n/**\n * Options for starting a screencast stream.\n */\nexport interface ScreencastOptions {\n  /** Image format (default: 'jpeg') */\n  format?: 'jpeg' | 'png';\n  /** JPEG quality 0-100 (default: 80) */\n  quality?: number;\n  /** Max width in pixels (default: 1280) */\n  maxWidth?: number;\n  /** Max height in pixels (default: 720) */\n  maxHeight?: number;\n  /** Capture every Nth frame (default: 1) */\n  everyNthFrame?: number;\n  /** Thread ID for thread-scoped screencasts (streams from thread's page) */\n  threadId?: string;\n}\n\n/**\n * Data for a single screencast frame.\n */\nexport interface ScreencastFrameData {\n  /** Base64-encoded image data */\n  data: string;\n  /** Frame timestamp in milliseconds */\n  timestamp: number;\n  /** Viewport information */\n  viewport: {\n    width: number;\n    height: number;\n    offsetTop?: number;\n    scrollOffsetX?: number;\n    scrollOffsetY?: number;\n    pageScaleFactor?: number;\n  };\n  /** CDP session ID for acknowledgment (handled internally) */\n  sessionId?: number;\n}\n\n/**\n * Events emitted by ScreencastStream.\n */\nexport interface ScreencastEvents {\n  /** Emitted when a new frame is received */\n  frame: (frame: ScreencastFrameData) => void;\n  /** Emitted when screencast stops */\n  stop: (reason: 'manual' | 'browser_closed' | 'error') => void;\n  /** Emitted on errors */\n  error: (error: Error) => void;\n  /** Emitted when the page URL changes (navigation detected) */\n  url: (url: string) => void;\n  /** Index signature for TypedEmitter compatibility */\n  [key: string]: (...args: any[]) => void;\n}\n\n/**\n * Default screencast options.\n */\nexport const SCREENCAST_DEFAULTS: Required<Omit<ScreencastOptions, 'threadId'>> = {\n  format: 'jpeg',\n  quality: 80,\n  maxWidth: 1280,\n  maxHeight: 720,\n  everyNthFrame: 1,\n};\n\n/**\n * Abstract CDP session interface.\n * Both Playwright's CDPSession and direct CDP connections implement this.\n */\nexport interface CdpSessionLike {\n  /** Send a CDP command */\n  send(method: string, params?: Record<string, unknown>): Promise<unknown>;\n  /** Register an event handler */\n  on(event: string, handler: (...args: any[]) => void): void;\n  /** Remove an event handler */\n  off?(event: string, handler: (...args: any[]) => void): void;\n  /** Detach the session */\n  detach?(): Promise<void>;\n}\n\n/**\n * Provider interface for getting CDP sessions.\n * Browser providers implement this to expose CDP access.\n */\nexport interface CdpSessionProvider {\n  /**\n   * Get a CDP session for screencast/input injection.\n   * The session should be attached to the current page.\n   */\n  getCdpSession(): Promise<CdpSessionLike>;\n\n  /**\n   * Check if the browser is currently running.\n   */\n  isBrowserRunning(): boolean;\n}\n","/**\n * CDP-based ScreencastStream implementation.\n *\n * This provides a unified screencast implementation that works with any\n * CDP session provider (Playwright, Puppeteer, direct CDP, etc.).\n */\n\nimport { EventEmitter } from 'node:events';\nimport type { CdpSessionLike, CdpSessionProvider, ScreencastFrameData, ScreencastOptions } from './types';\nimport { SCREENCAST_DEFAULTS } from './types';\n\n/**\n * CDP screencast frame event data from Page.screencastFrame\n */\ninterface CdpScreencastFrame {\n  data: string;\n  sessionId: number;\n  metadata?: {\n    deviceWidth?: number;\n    deviceHeight?: number;\n    offsetTop?: number;\n    scrollOffsetX?: number;\n    scrollOffsetY?: number;\n    pageScaleFactor?: number;\n    timestamp?: number;\n  };\n}\n\n/**\n * ScreencastStream wraps CDP screencast with an event emitter interface.\n *\n * Works with any CDP session provider (Playwright, Puppeteer, direct CDP).\n *\n * @example\n * ```typescript\n * const stream = new ScreencastStream(cdpProvider, { quality: 80 });\n * stream.on('frame', (frame) => {\n *   console.log(`Frame: ${frame.viewport.width}x${frame.viewport.height}`);\n * });\n * await stream.start();\n * // Later...\n * await stream.stop();\n * ```\n */\nexport class ScreencastStream extends EventEmitter {\n  /** Whether screencast is currently active */\n  private active: boolean = false;\n\n  /** Resolved options with defaults applied (excludes threadId which is only used for page selection) */\n  private options: Required<Omit<ScreencastOptions, 'threadId'>>;\n\n  /** CDP session provider */\n  private provider: CdpSessionProvider;\n\n  /** Current CDP session */\n  private cdpSession: CdpSessionLike | null = null;\n\n  /** Frame handler reference (for cleanup) */\n  private frameHandler: ((params: CdpScreencastFrame) => void) | null = null;\n\n  /**\n   * Creates a new ScreencastStream.\n   *\n   * @param provider - CDP session provider (browser instance)\n   * @param options - Screencast configuration options\n   */\n  constructor(provider: CdpSessionProvider, options?: ScreencastOptions) {\n    super();\n    this.provider = provider;\n    // Extract threadId (used by caller for page selection) and merge remaining options\n    const { threadId: _, ...cdpOptions } = options ?? {};\n    this.options = { ...SCREENCAST_DEFAULTS, ...cdpOptions };\n  }\n\n  /**\n   * Start the screencast.\n   * If already active, returns immediately.\n   */\n  async start(): Promise<void> {\n    if (this.active) {\n      return;\n    }\n\n    if (!this.provider.isBrowserRunning()) {\n      throw new Error('Browser is not running');\n    }\n\n    try {\n      // Get CDP session from provider\n      this.cdpSession = await this.provider.getCdpSession();\n\n      // Set up frame handler\n      this.frameHandler = (params: CdpScreencastFrame) => {\n        const frameData: ScreencastFrameData = {\n          data: params.data,\n          // CDP provides timestamp in seconds, convert to milliseconds for consistency\n          timestamp: params.metadata?.timestamp ? params.metadata.timestamp * 1000 : Date.now(),\n          viewport: {\n            width: params.metadata?.deviceWidth ?? 0,\n            height: params.metadata?.deviceHeight ?? 0,\n            offsetTop: params.metadata?.offsetTop,\n            scrollOffsetX: params.metadata?.scrollOffsetX,\n            scrollOffsetY: params.metadata?.scrollOffsetY,\n            pageScaleFactor: params.metadata?.pageScaleFactor,\n          },\n          sessionId: params.sessionId,\n        };\n\n        this.emit('frame', frameData);\n\n        // Acknowledge frame to continue receiving\n        this.acknowledgeFrame(params.sessionId);\n      };\n\n      this.cdpSession.on('Page.screencastFrame', this.frameHandler);\n\n      // Start screencast via CDP\n      try {\n        await this.cdpSession.send('Page.startScreencast', {\n          format: this.options.format,\n          quality: this.options.quality,\n          maxWidth: this.options.maxWidth,\n          maxHeight: this.options.maxHeight,\n          everyNthFrame: this.options.everyNthFrame,\n        });\n      } catch (startError) {\n        // Clean up handler before re-throwing to prevent resource leak\n        if (this.cdpSession?.off) {\n          try {\n            this.cdpSession.off('Page.screencastFrame', this.frameHandler);\n          } catch {\n            // Ignore cleanup errors\n          }\n        }\n        this.frameHandler = null;\n        this.cdpSession = null;\n        throw startError;\n      }\n\n      this.active = true;\n    } catch (error) {\n      const err = error instanceof Error ? error : new Error(String(error));\n      this.emit('error', err);\n      throw err;\n    }\n  }\n\n  /**\n   * Acknowledge a frame to CDP (required to continue receiving frames).\n   */\n  private acknowledgeFrame(sessionId: number): void {\n    if (!this.cdpSession) return;\n\n    this.cdpSession.send('Page.screencastFrameAck', { sessionId }).catch(() => {\n      // Ignore ack errors - session may be closed\n    });\n  }\n\n  /**\n   * Stop the screencast and release resources.\n   * Safe to call even if browser/CDP session is already closed.\n   */\n  async stop(): Promise<void> {\n    if (!this.active) {\n      return;\n    }\n\n    this.active = false;\n    let hadError = false;\n\n    // Clean up handler regardless of CDP state\n    if (this.cdpSession && this.frameHandler && this.cdpSession.off) {\n      try {\n        this.cdpSession.off('Page.screencastFrame', this.frameHandler);\n      } catch {\n        // Ignore - session may be dead\n      }\n    }\n    this.frameHandler = null;\n\n    // Try to stop screencast via CDP (may fail if browser closed)\n    if (this.cdpSession) {\n      try {\n        await this.cdpSession.send('Page.stopScreencast');\n      } catch {\n        // Browser/session already closed - this is expected in external close scenarios\n        hadError = true;\n      }\n      this.cdpSession = null;\n    }\n\n    this.emit('stop', hadError ? 'error' : 'manual');\n  }\n\n  /**\n   * Check if screencast is currently active.\n   */\n  isActive(): boolean {\n    return this.active;\n  }\n\n  /**\n   * Emit a URL update event.\n   * Browser providers call this when navigation is detected.\n   */\n  emitUrl(url: string): void {\n    this.emit('url', url);\n  }\n\n  /**\n   * Reconnect the screencast by stopping and restarting.\n   * Use this when the active page/tab changes.\n   *\n   * @returns Promise that resolves when reconnection is complete\n   * @throws Error if reconnection fails (also emits 'error' event)\n   */\n  async reconnect(): Promise<void> {\n    // Clean up existing session\n    if (this.cdpSession && this.frameHandler && this.cdpSession.off) {\n      try {\n        this.cdpSession.off('Page.screencastFrame', this.frameHandler);\n      } catch {\n        // Ignore - session may be dead\n      }\n    }\n    this.frameHandler = null;\n\n    // Try to stop screencast on old session (may fail if session is dead)\n    if (this.cdpSession) {\n      try {\n        await this.cdpSession.send('Page.stopScreencast');\n      } catch {\n        // Old session may already be detached - this is expected\n      }\n      this.cdpSession = null;\n    }\n\n    // Mark as inactive so start() will work\n    this.active = false;\n\n    // Restart with fresh session from provider\n    try {\n      await this.start();\n    } catch (error) {\n      const err = error instanceof Error ? error : new Error(String(error));\n      console.error('[ScreencastStream.reconnect] Failed to reconnect:', err);\n      // Don't emit 'error' here - start() already emits it before rejecting\n      throw err;\n    }\n  }\n}\n"]}