@agimon-ai/browse-tool 0.2.19 → 0.2.21

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"streamable-http-DfMXvzhA.cjs","names":["WORKSPACE_MARKERS","sanitized: Attributes","headers: Record<string, string>","resolveWorkspaceRoot","path","PortRegistryService","DEFAULT_REGISTRY_PATH","PortRegistryStateSchema","TelemetryService","AsyncLocalStorage","resourceAttributes: Attributes","OTLPTraceExporter","NodeTracerProvider","SimpleSpanProcessor","BatchSpanProcessor","trace","context","SpanKind","SpanStatusCode","ExtensionTaskQueue","webSocketHub?: WebSocketHub","telemetry: ITelemetryService","task: ExtensionTask","taskPush: TaskPush","ExtensionToolDelegator","taskQueue: ExtensionTaskQueue","DEFAULT_SCREEN: ScreenDimensions","AutomationRunner","browserService: IBrowserService","specRunner: ISpecRunner","specMetadataService: ISpecMetadataService","setupRunner: ISetupRunner","webServerManager: IWebServerManager","pageRegistry: IPageRegistry","session: AutomationRunSession","page: Page","context: BrowserContext","browser: Browser","serverResult: ServerStartResult | undefined","setupResult: SetupResult | undefined","cdpSession: CDPSession | undefined","pid: number | undefined","userDataDir: string","BrowserLockManager","os","lockInfo: LockInfo","path","fs","chromeForTestingConfig.version","version","ChromeForTestingService","os","Readable","proc: ChildProcess","WORKSPACE_MARKERS","chromeForTestingConfig.dockerImage","chromeForTestingConfig.dockerPlatform","resolveWorkspaceRoot","path","pathExists","fs","version","platform","descendants: number[]","childPids: number[]","resolve","path","os","args: string[]","DOCKER_VM_ALIASES","chromeForTestingConfig.dockerImage","chromeForTestingConfig.dockerBrowserBinary","DEBUG_EXTENSION_RECORDING_STDOUT","BrowserService","profileService: IProfileService","pageRegistry: IPageRegistry","lockManager: IBrowserLockManager","chromeForTesting: IChromeForTestingService","pageMonitor?: IPageMonitorService","webSocketHub?: WebSocketHub","extensionSessionRegistry?: IExtensionSessionRegistry","extensionTaskQueue?: ExtensionTaskQueue","browser: Awaited<ReturnType<typeof browserTypeInstance.launch>>","context","browserInstance: BrowserInstance","path","fs","dockerVolumeArgs: string[]","defaultArgs: string[]","lastError: Error | undefined","lastDockerRetryCleanupStatus: string | undefined","platform","chromeForTestingConfig.dockerPlatform","stack: string[]","args: string[]","os","CHROME_PATHS: Record<string, string[]>","recordingError: Error | undefined","stopResult: ExtensionTaskResult","payload: { videoBase64?: string }","chunks: string[]","exitCode: number | null","exitSignal: NodeJS.Signals | null","processError: Error | undefined","stableSince: number | null","firstConnectedAt: number | null","resolve","releaseQueue: (() => void) | undefined","pageId","chromium","firefox","webkit","options: NonNullable<Parameters<Browser['newContext']>[0]>","path","snippets: SavedCodeSnippet[]","ElementLocatorService","context","parts: string[]","ExtensionPageProxy","PAGE_PROXY_BRAND","taskQueue: ExtensionTaskQueue","LocatorProxy","ExtensionSessionRegistry","session: ExtensionSession","ExtensionSpecRunner","bundler: ISpecBundlerService","pageProxy: IExtensionPageProxy","sessionRegistry: IExtensionSessionRegistry","getCollectedSuite","testResults: ExtensionTestResult[]","fixtures: ExtensionTestFixtures","DEFAULT_PORT_RANGE","HttpBrowserClient","request: ExecuteRequest","HttpServerHealthCheck","__filename","__dirname","path","WORKSPACE_MARKERS","DEFAULT_ENVIRONMENT","LOG_PREFIX","fs","resolveWorkspaceRoot","HttpServerManager","healthCheck: HttpServerHealthCheck","portRegistry: PortRegistryService","processRegistry: ProcessRegistryService","ProcessRegistryService","errors: string[]","healthResult: HealthCheckResult","resolve","startupError","collectedErrors: Error[]","IdleCleanupService","browserService: IBrowserService","pageRegistry: IPageRegistry","McpSessionTracker","PageMonitorService","state: PageMonitorState","entry: NetworkRequestEntry","entry: ConsoleMessageEntry","PageRegistry","webSocketHub?: WebSocketHub","entry: PageEntry","PauseController","resolve","ProfileService","profiles: BrowserProfile[]","fullProfile: BrowserProfile","updatedProfile: BrowserProfile","RemoteToolExecutor","httpClient: HttpBrowserClient","SetupRunner","bundler: ISpecBundlerService","bundleResult: { outputPath: string; cleanup: () => Promise<void> } | null","context","module","LOG_PREFIX","specPath: string","buildErrors: string","operation: string","targetPath: string","SpecBundlerService","SpecDiscoveryService","projects: E2EProject[]","project: E2EProject","specs: SpecInfo[]","filteredSpecs: SpecInfo[]","configs: string[]","specs: string[]","SpecMetadataService","bundler: ISpecBundlerService","config: PlaywrightMcpConfig","args: Record<string, unknown>","SpecRunner","bundler: ISpecBundlerService","getCollectedSuite","testResults: TestResult[]","fixtures: TestFixtures","SkipTestError","fixtures: TestFixturesEnhanced","StealthLauncher","profileService: IProfileService","pageRegistry: IPageRegistry","lockManager: IBrowserLockManager","platform","os","paths: Record<string, string[]>","fs","path","instance: StealthBrowserInstance","ToolExecutor","pageRegistry: IPageRegistry","browserService: IBrowserService","delegator: ExtensionToolDelegator","telemetry: ITelemetryService","SpanStatusCode","WebServerManager","parts: string[]","resolve","z","WebSocketHub","connection: ExtensionConnection","message: PageCreated","message: PageRemoved","message: SessionAck","BaseTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","ClickTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","CloseBrowserTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","ClosePageTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","profileService: IProfileService","CreateProfileTool","profileService: IProfileService","DeleteProfileTool","profileService: IProfileService","DiscoverSpecsTool","specDiscoveryService: ISpecDiscoveryService","output","DragTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","EmulateTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","context","appliedSettings: Record<string, unknown>","EvaluateScriptTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","serializedResult: string","ExpectTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","extensionPageProxy: IExtensionPageProxy","expect","FillTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","GetNetworkRequestTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","pageMonitor: IPageMonitorService","GoBackTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","GoForwardTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","HandleDialogTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","HoverTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","path","LaunchBrowserTool","browserService: IBrowserService","stealthLauncher: IStealthLauncher","parsed: URL","runArgs: string[]","env: Record<string, string>","vnc: { vncUrl: string; noVncUrl: string } | undefined","PortRegistryService","ListConsoleMessagesTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","pageMonitor: IPageMonitorService","ListNetworkRequestsTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","pageMonitor: IPageMonitorService","ListPagesTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","ListProfilesTool","profileService: IProfileService","ListSnippetsTool","snippetService: ICodeSnippetService","NavigateTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","NewPageTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","pageId","delegatedPayload: { url?: string; title?: string; tabId?: number }","pageEntry","PdfTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","extensionTaskQueue: ExtensionTaskQueue","PressKeyTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","ReloadTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","ResizePageTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","RunCodeTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","extensionPageProxy: IExtensionPageProxy","snippetService: ICodeSnippetService","context","savedSnippet: SavedCodeSnippet | undefined","RunSpecTool","automationRunner: IAutomationRunner","specMetadataService: ISpecMetadataService","extensionSpecRunner: IExtensionSpecRunner","extensionPageProxy: IExtensionPageProxy","specBundlerService: ISpecBundlerService","testFilter: TestFilter","result: ExtensionSpecExecutionResult","SaveProfileStateTool","profileService: IProfileService","browserService: IBrowserService","extensionPageProxy: IExtensionPageProxy","ScreenshotTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","SelectPageTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","SelectTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","options: { value?: string; label?: string; index?: number }[]","SnapshotTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","StartTraceTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","context","StartRecordingTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","path","StopTraceTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","context","path","StopRecordingTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","TypeTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","UploadFileTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","elementLocator: IElementLocatorService","WaitForTool","pageRegistry: IPageRegistry","browserService: IBrowserService","toolExecutor: ToolExecutor","resolve","ContainerModule","PortRegistryService","ProcessRegistryService","container","Container","TransportConnectionError","StdioServerTransport","chunks: Buffer[]","context","StreamableHTTPServerTransport","parsedBody: unknown","transport: StreamableHTTPServerTransport | undefined"],"sources":["../src/constants/service-ids.ts","../src/constants/playwright-types.ts","../src/services/TelemetryService.ts","../src/constants/timeouts.ts","../src/services/ExtensionTaskQueue.ts","../src/services/ExtensionToolDelegator.ts","../src/services/AutomationRunner.ts","../src/services/BrowserLockManager.ts","../src/config/chrome-for-testing.json","../src/services/ChromeForTestingService.ts","../src/utils/dockerChromeForTesting.ts","../src/utils/processTree.ts","../src/utils/proxyAuthExtension.ts","../src/services/BrowserService.ts","../src/services/CodeSnippetService.ts","../src/services/ElementLocatorService.ts","../src/services/ExtensionPageProxy.ts","../src/services/ExtensionSessionRegistry.ts","../src/services/ExtensionSpecRunner.ts","../src/utils/networkConfig.ts","../src/services/HttpBrowserClient.ts","../src/services/HttpServerHealthCheck.ts","../src/services/HttpServerManager.ts","../src/services/IdleCleanupService.ts","../src/services/McpSessionTracker.ts","../src/services/PageMonitorService.ts","../src/services/PageRegistry.ts","../src/services/PauseController.ts","../src/services/ProfileService.ts","../src/services/RemoteToolExecutor.ts","../src/services/SetupRunner.ts","../src/services/SpecBundlerService.ts","../src/services/SpecDiscoveryService.ts","../src/services/SpecMetadataService.ts","../src/services/SpecRunner.ts","../src/services/StealthLauncher.ts","../src/services/ToolExecutor.ts","../src/services/WebServerManager.ts","../src/protocol/extension-messages.ts","../src/services/WebSocketHub.ts","../src/tools/BaseTool.ts","../src/tools/ClickTool.ts","../src/tools/CloseBrowserTool.ts","../src/tools/ClosePageTool.ts","../src/tools/CreateProfileTool.ts","../src/tools/DeleteProfileTool.ts","../src/tools/DiscoverSpecsTool.ts","../src/tools/DragTool.ts","../src/tools/EmulateTool.ts","../src/tools/EvaluateScriptTool.ts","../src/tools/ExpectTool.ts","../src/tools/FillTool.ts","../src/tools/GetNetworkRequestTool.ts","../src/tools/GoBackTool.ts","../src/tools/GoForwardTool.ts","../src/tools/HandleDialogTool.ts","../src/tools/HoverTool.ts","../src/tools/LaunchBrowserTool.ts","../src/tools/ListConsoleMessagesTool.ts","../src/tools/ListNetworkRequestsTool.ts","../src/tools/ListPagesTool.ts","../src/tools/ListProfilesTool.ts","../src/tools/ListSnippetsTool.ts","../src/tools/NavigateTool.ts","../src/tools/NewPageTool.ts","../src/tools/PdfTool.ts","../src/tools/PressKeyTool.ts","../src/tools/ReloadTool.ts","../src/tools/ResizePageTool.ts","../src/tools/RunCodeTool.ts","../src/tools/RunSpecTool.ts","../src/tools/SaveProfileStateTool.ts","../src/tools/ScreenshotTool.ts","../src/tools/SelectPageTool.ts","../src/tools/SelectTool.ts","../src/tools/SnapshotTool.ts","../src/tools/StartTraceTool.ts","../src/tools/StartRecordingTool.ts","../src/tools/StopTraceTool.ts","../src/tools/StopRecordingTool.ts","../src/tools/TypeTool.ts","../src/tools/UploadFileTool.ts","../src/tools/WaitForTool.ts","../src/container/index.ts","../src/transports/stdio.ts","../src/transports/streamable-http.ts"],"sourcesContent":["/**\n * Service identifier strings for InversifyJS dependency injection.\n * Used with Symbol.for() to create IoC container binding tokens.\n *\n * DESIGN PATTERNS:\n * - Named constants pattern to eliminate magic strings\n * - Single source of truth for all DI token identifiers\n *\n * CODING STANDARDS:\n * - All constants use SCREAMING_SNAKE_CASE\n * - Group constants by domain/category with comments\n * - Export individually for tree-shaking\n *\n * AVOID:\n * - Inline string literals in Symbol.for() calls\n * - Duplicating identifier strings across files\n */\n\n// Core Services\nexport const SERVICE_PROFILE = 'ProfileService';\nexport const SERVICE_PAGE_REGISTRY = 'PageRegistry';\nexport const SERVICE_BROWSER = 'BrowserService';\nexport const SERVICE_ELEMENT_LOCATOR = 'ElementLocatorService';\nexport const SERVICE_PAGE_MONITOR = 'PageMonitorService';\nexport const SERVICE_PAUSE_CONTROLLER = 'PauseController';\nexport const SERVICE_AUTOMATION_RUNNER = 'AutomationRunner';\nexport const SERVICE_SPEC_RUNNER = 'SpecRunner';\nexport const SERVICE_SPEC_BUNDLER = 'SpecBundlerService';\nexport const SERVICE_SPEC_DISCOVERY = 'SpecDiscoveryService';\nexport const SERVICE_SPEC_METADATA = 'SpecMetadataService';\nexport const SERVICE_SETUP_RUNNER = 'SetupRunner';\nexport const SERVICE_WEB_SERVER_MANAGER = 'WebServerManager';\nexport const SERVICE_LOGGER = 'Logger';\nexport const SERVICE_TELEMETRY = 'TelemetryService';\nexport const SERVICE_CODE_SNIPPET = 'CodeSnippetService';\n\n// HTTP Services\nexport const SERVICE_HTTP_HEALTH_CHECK = 'HttpServerHealthCheck';\nexport const SERVICE_HTTP_SERVER_MANAGER = 'HttpServerManager';\nexport const SERVICE_HTTP_BROWSER_CLIENT = 'HttpBrowserClient';\nexport const SERVICE_REMOTE_TOOL_EXECUTOR = 'RemoteToolExecutor';\nexport const SERVICE_PROCESS_REGISTRY = 'ProcessRegistryService';\n\n// Extension Services\nexport const SERVICE_EXTENSION_TASK_QUEUE = 'ExtensionTaskQueue';\nexport const SERVICE_EXTENSION_TOOL_DELEGATOR = 'ExtensionToolDelegator';\nexport const SERVICE_TOOL_EXECUTOR = 'ToolExecutor';\nexport const SERVICE_EXTENSION_SESSION_REGISTRY = 'ExtensionSessionRegistry';\nexport const SERVICE_EXTENSION_PAGE_PROXY = 'ExtensionPageProxy';\nexport const SERVICE_EXTENSION_SPEC_RUNNER = 'ExtensionSpecRunner';\n\n// Stealth Services\nexport const SERVICE_STEALTH_LAUNCHER = 'StealthLauncher';\n\n// Lock Management\nexport const SERVICE_BROWSER_LOCK_MANAGER = 'BrowserLockManager';\n\n// MCP Session Tracking\nexport const SERVICE_MCP_SESSION_TRACKER = 'McpSessionTracker';\n\n// Chrome for Testing\nexport const SERVICE_CHROME_FOR_TESTING = 'ChromeForTestingService';\n\n// WebSocket Hub\nexport const SERVICE_WEBSOCKET_HUB = 'WebSocketHub';\n\n// Idle Cleanup\nexport const SERVICE_IDLE_CLEANUP = 'IdleCleanupService';\n\n// Port Registry\nexport const SERVICE_PORT_REGISTRY = 'PortRegistryService';\n\n// Tools (multiInject identifier)\nexport const SERVICE_TOOL = 'Tool';\n","/**\n * InversifyJS DI container binding tokens.\n * Maps service names to unique Symbol identifiers for dependency injection.\n *\n * DESIGN PATTERNS:\n * - Token map pattern for IoC container bindings\n * - Uses named constants from service-ids to avoid magic strings\n * - Frozen object (as const) to prevent runtime modification\n *\n * CODING STANDARDS:\n * - Import service ID constants instead of using inline strings\n * - Keep in sync with container module registrations\n * - Use Symbol.for() for cross-module symbol sharing\n *\n * AVOID:\n * - Adding entries without corresponding container bindings\n * - Using Symbol() instead of Symbol.for() (breaks cross-module resolution)\n */\n\nimport {\n SERVICE_AUTOMATION_RUNNER,\n SERVICE_BROWSER,\n SERVICE_BROWSER_LOCK_MANAGER,\n SERVICE_CHROME_FOR_TESTING,\n SERVICE_CODE_SNIPPET,\n SERVICE_ELEMENT_LOCATOR,\n SERVICE_EXTENSION_PAGE_PROXY,\n SERVICE_EXTENSION_SESSION_REGISTRY,\n SERVICE_EXTENSION_SPEC_RUNNER,\n SERVICE_EXTENSION_TASK_QUEUE,\n SERVICE_EXTENSION_TOOL_DELEGATOR,\n SERVICE_HTTP_BROWSER_CLIENT,\n SERVICE_HTTP_HEALTH_CHECK,\n SERVICE_HTTP_SERVER_MANAGER,\n SERVICE_IDLE_CLEANUP,\n SERVICE_LOGGER,\n SERVICE_TELEMETRY,\n SERVICE_MCP_SESSION_TRACKER,\n SERVICE_PAGE_MONITOR,\n SERVICE_PAGE_REGISTRY,\n SERVICE_PAUSE_CONTROLLER,\n SERVICE_PROCESS_REGISTRY,\n SERVICE_PORT_REGISTRY,\n SERVICE_PROFILE,\n SERVICE_REMOTE_TOOL_EXECUTOR,\n SERVICE_SETUP_RUNNER,\n SERVICE_SPEC_BUNDLER,\n SERVICE_SPEC_DISCOVERY,\n SERVICE_SPEC_METADATA,\n SERVICE_SPEC_RUNNER,\n SERVICE_STEALTH_LAUNCHER,\n SERVICE_TOOL,\n SERVICE_TOOL_EXECUTOR,\n SERVICE_WEBSOCKET_HUB,\n SERVICE_WEB_SERVER_MANAGER,\n} from './service-ids.js';\n\nexport const PLAYWRIGHT_TYPES = {\n // Services\n ProfileService: Symbol.for(SERVICE_PROFILE),\n PageRegistry: Symbol.for(SERVICE_PAGE_REGISTRY),\n BrowserService: Symbol.for(SERVICE_BROWSER),\n ElementLocatorService: Symbol.for(SERVICE_ELEMENT_LOCATOR),\n PageMonitorService: Symbol.for(SERVICE_PAGE_MONITOR),\n PauseController: Symbol.for(SERVICE_PAUSE_CONTROLLER),\n AutomationRunner: Symbol.for(SERVICE_AUTOMATION_RUNNER),\n SpecRunner: Symbol.for(SERVICE_SPEC_RUNNER),\n SpecBundlerService: Symbol.for(SERVICE_SPEC_BUNDLER),\n SpecDiscoveryService: Symbol.for(SERVICE_SPEC_DISCOVERY),\n SpecMetadataService: Symbol.for(SERVICE_SPEC_METADATA),\n SetupRunner: Symbol.for(SERVICE_SETUP_RUNNER),\n WebServerManager: Symbol.for(SERVICE_WEB_SERVER_MANAGER),\n Logger: Symbol.for(SERVICE_LOGGER),\n TelemetryService: Symbol.for(SERVICE_TELEMETRY),\n CodeSnippetService: Symbol.for(SERVICE_CODE_SNIPPET),\n // HTTP Services\n HttpServerHealthCheck: Symbol.for(SERVICE_HTTP_HEALTH_CHECK),\n HttpServerManager: Symbol.for(SERVICE_HTTP_SERVER_MANAGER),\n HttpBrowserClient: Symbol.for(SERVICE_HTTP_BROWSER_CLIENT),\n RemoteToolExecutor: Symbol.for(SERVICE_REMOTE_TOOL_EXECUTOR),\n // Extension Services\n ExtensionTaskQueue: Symbol.for(SERVICE_EXTENSION_TASK_QUEUE),\n ExtensionToolDelegator: Symbol.for(SERVICE_EXTENSION_TOOL_DELEGATOR),\n ToolExecutor: Symbol.for(SERVICE_TOOL_EXECUTOR),\n // Stealth Services\n StealthLauncher: Symbol.for(SERVICE_STEALTH_LAUNCHER),\n // Lock Management\n BrowserLockManager: Symbol.for(SERVICE_BROWSER_LOCK_MANAGER),\n // Extension Session Management\n ExtensionSessionRegistry: Symbol.for(SERVICE_EXTENSION_SESSION_REGISTRY),\n // Extension Page Proxy\n ExtensionPageProxy: Symbol.for(SERVICE_EXTENSION_PAGE_PROXY),\n // Extension Spec Runner\n ExtensionSpecRunner: Symbol.for(SERVICE_EXTENSION_SPEC_RUNNER),\n // MCP Session Tracking\n McpSessionTracker: Symbol.for(SERVICE_MCP_SESSION_TRACKER),\n // Chrome for Testing\n ChromeForTestingService: Symbol.for(SERVICE_CHROME_FOR_TESTING),\n // WebSocket Hub\n WebSocketHub: Symbol.for(SERVICE_WEBSOCKET_HUB),\n // Idle Cleanup\n IdleCleanupService: Symbol.for(SERVICE_IDLE_CLEANUP),\n // Port Registry\n PortRegistryService: Symbol.for(SERVICE_PORT_REGISTRY),\n // Process Registry\n ProcessRegistryService: Symbol.for(SERVICE_PROCESS_REGISTRY),\n // Tools (multiInject identifier)\n Tool: Symbol.for(SERVICE_TOOL),\n} as const;\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport 'reflect-metadata/lite';\nimport type { Context } from '@opentelemetry/api';\nimport { SpanKind, SpanStatusCode, context, trace, type Attributes, type Span } from '@opentelemetry/api';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';\nimport {\n DEFAULT_REGISTRY_PATH,\n PortRegistryService,\n PortRegistryStateSchema,\n normalizeRepositoryPath,\n} from '@agimon-ai/foundation-port-registry';\nimport { createNodeTelemetry, type NodeTelemetryHandle } from '@agimon-ai/log-sink-mcp';\nimport { injectable } from 'inversify';\n\ntype TelemetryLogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\nexport interface TelemetryServiceOptions {\n env?: NodeJS.ProcessEnv;\n serviceName?: string;\n serviceVersion?: string;\n traceExporter?: TraceExporterLike;\n logExporter?: LogExporterLike;\n}\n\nexport interface TelemetrySpanOptions {\n attributes?: Attributes;\n kind?: SpanKind;\n}\n\nexport interface TelemetryLogOptions {\n attributes?: Attributes;\n context?: Context;\n exception?: unknown;\n}\n\nexport interface ITelemetryService {\n isEnabled(): boolean;\n getActiveTraceContext(): { traceId?: string; spanId?: string };\n runInSpan<T>(\n name: string,\n options: TelemetrySpanOptions,\n callback: (span: Span | undefined) => Promise<T> | T,\n ): Promise<T>;\n log(level: TelemetryLogLevel, message: string, options?: TelemetryLogOptions): void;\n forceFlush(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\ninterface SignalExporterConfig {\n url?: string;\n headers?: Record<string, string>;\n timeoutMillis?: number;\n}\n\nexport interface BrowserTelemetryConfig {\n enabled: boolean;\n logsEndpoint?: string;\n tracesEndpoint?: string;\n serviceName: string;\n}\n\ninterface TraceExporterLike {\n export: (...args: unknown[]) => void;\n shutdown: () => Promise<void>;\n forceFlush?: () => Promise<void>;\n}\n\ninterface LogExporterLike {\n export: (...args: unknown[]) => void;\n shutdown: () => Promise<void>;\n forceFlush?: () => Promise<void>;\n}\n\nconst DEFAULT_SERVICE_NAME = 'browse-tool-http';\nconst DEFAULT_LOG_SINK_SERVICE_NAME = 'log-sink-mcp-http';\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0.0.0.0']);\n\nfunction isTruthy(value: string | undefined): boolean {\n return value === '1' || value === 'true';\n}\n\nfunction toStringValue(value: unknown): string | number | boolean {\n if (Array.isArray(value)) {\n return JSON.stringify(value);\n }\n\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n\n return JSON.stringify(value);\n}\n\nfunction sanitizeAttributes(attributes: Attributes | undefined): Attributes | undefined {\n if (!attributes) {\n return undefined;\n }\n\n const sanitized: Attributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n\n sanitized[key] = toStringValue(value);\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined;\n}\n\nfunction parseHeaders(rawHeaders: string | undefined): Record<string, string> | undefined {\n if (!rawHeaders) {\n return undefined;\n }\n\n const headers: Record<string, string> = {};\n for (const pair of rawHeaders.split(',')) {\n const [rawKey, ...rawValueParts] = pair.split('=');\n const key = rawKey?.trim();\n const value = rawValueParts.join('=').trim();\n if (!key || !value) {\n continue;\n }\n headers[key] = value;\n }\n\n return Object.keys(headers).length > 0 ? headers : undefined;\n}\n\nfunction parseTimeout(rawTimeout: string | undefined): number | undefined {\n if (!rawTimeout) {\n return undefined;\n }\n\n const timeout = Number(rawTimeout);\n return Number.isFinite(timeout) && timeout > 0 ? timeout : undefined;\n}\n\nfunction withSignalPath(baseUrl: string, signal: 'traces' | 'logs'): string {\n const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n if (normalizedBaseUrl.endsWith(`/v1/${signal}/`) || normalizedBaseUrl.endsWith(`/v1/${signal}`)) {\n return baseUrl;\n }\n\n return new URL(`v1/${signal}`, normalizedBaseUrl).toString();\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return LOOPBACK_HOSTS.has(hostname);\n}\n\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let currentDir = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(path.join(currentDir, marker))) {\n return currentDir;\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return process.cwd();\n }\n\n currentDir = parentDir;\n }\n}\n\nfunction resolveRegistryBaseEndpoint(env: NodeJS.ProcessEnv, startPath = process.cwd()): string | undefined {\n try {\n const registryPath = PortRegistryService.resolveRegistryPath(env.PORT_REGISTRY_PATH ?? DEFAULT_REGISTRY_PATH);\n const registryRaw = readFileSync(registryPath, 'utf8');\n const registryState = PortRegistryStateSchema.parse(JSON.parse(registryRaw));\n const repositoryPath = normalizeRepositoryPath(resolveWorkspaceRoot(startPath));\n const environment = env.NODE_ENV || 'development';\n const serviceName = env.BROWSE_TOOL_OTEL_LOG_SINK_SERVICE_NAME ?? DEFAULT_LOG_SINK_SERVICE_NAME;\n\n const entry = registryState.entries.find(\n (candidate) =>\n candidate.repositoryPath === repositoryPath &&\n candidate.serviceName === serviceName &&\n candidate.serviceType === 'tool' &&\n (candidate.environment ?? 'development') === environment,\n );\n\n if (!entry) {\n return undefined;\n }\n\n const healthCheckUrl =\n typeof entry.metadata?.healthCheckUrl === 'string' && entry.metadata.healthCheckUrl.length > 0\n ? entry.metadata.healthCheckUrl\n : undefined;\n if (healthCheckUrl) {\n return new URL(healthCheckUrl).origin;\n }\n\n const host = entry.host === '0.0.0.0' ? '127.0.0.1' : entry.host;\n return `http://${host}:${entry.port}`;\n } catch {\n return undefined;\n }\n}\n\nfunction rewriteBrowserReachableEndpoint(baseUrl: string, requestOrigin: string): string {\n try {\n const endpointUrl = new URL(baseUrl);\n const requestUrl = new URL(requestOrigin);\n if (!isLoopbackHost(endpointUrl.hostname) || isLoopbackHost(requestUrl.hostname)) {\n return endpointUrl.toString();\n }\n\n endpointUrl.hostname = requestUrl.hostname;\n return endpointUrl.toString();\n } catch {\n return baseUrl;\n }\n}\n\nexport function resolveSignalEndpoint(env: NodeJS.ProcessEnv, signal: 'traces' | 'logs'): string | undefined {\n const explicitEndpoint =\n signal === 'traces' ? env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT : env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;\n if (explicitEndpoint) {\n return explicitEndpoint;\n }\n\n const explicitSharedEndpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT;\n if (explicitSharedEndpoint) {\n return withSignalPath(explicitSharedEndpoint, signal);\n }\n\n const registrySharedEndpoint = resolveRegistryBaseEndpoint(env);\n return registrySharedEndpoint ? withSignalPath(registrySharedEndpoint, signal) : undefined;\n}\n\nexport function resolveBrowserSignalEndpoint(\n env: NodeJS.ProcessEnv,\n signal: 'traces' | 'logs',\n requestOrigin: string,\n): string | undefined {\n const explicitBrowserEndpoint =\n signal === 'traces' ? env.BROWSE_TOOL_OTEL_BROWSER_TRACES_ENDPOINT : env.BROWSE_TOOL_OTEL_BROWSER_LOGS_ENDPOINT;\n if (explicitBrowserEndpoint) {\n return rewriteBrowserReachableEndpoint(explicitBrowserEndpoint, requestOrigin);\n }\n\n const explicitBrowserSharedEndpoint = env.BROWSE_TOOL_OTEL_BROWSER_ENDPOINT;\n if (explicitBrowserSharedEndpoint) {\n return rewriteBrowserReachableEndpoint(withSignalPath(explicitBrowserSharedEndpoint, signal), requestOrigin);\n }\n\n const hostSideEndpoint = resolveSignalEndpoint(env, signal);\n return hostSideEndpoint ? rewriteBrowserReachableEndpoint(hostSideEndpoint, requestOrigin) : undefined;\n}\n\nexport function resolveBrowserTelemetryConfig(env: NodeJS.ProcessEnv, requestOrigin: string): BrowserTelemetryConfig {\n const serviceName =\n env.BROWSE_TOOL_OTEL_BROWSER_SERVICE_NAME ?? `${env.OTEL_SERVICE_NAME ?? DEFAULT_SERVICE_NAME}-extension`;\n const tracesEndpoint = resolveBrowserSignalEndpoint(env, 'traces', requestOrigin);\n const logsEndpoint = resolveBrowserSignalEndpoint(env, 'logs', requestOrigin);\n\n return {\n enabled: Boolean(tracesEndpoint || logsEndpoint),\n tracesEndpoint,\n logsEndpoint,\n serviceName,\n };\n}\n\nfunction resolveSignalExporterConfig(env: NodeJS.ProcessEnv, signal: 'traces' | 'logs'): SignalExporterConfig {\n const signalHeaders =\n signal === 'traces' ? env.OTEL_EXPORTER_OTLP_TRACES_HEADERS : env.OTEL_EXPORTER_OTLP_LOGS_HEADERS;\n const signalTimeout =\n signal === 'traces' ? env.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT : env.OTEL_EXPORTER_OTLP_LOGS_TIMEOUT;\n\n return {\n url: resolveSignalEndpoint(env, signal),\n headers: parseHeaders(signalHeaders ?? env.OTEL_EXPORTER_OTLP_HEADERS),\n timeoutMillis: parseTimeout(signalTimeout ?? env.OTEL_EXPORTER_OTLP_TIMEOUT),\n };\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction buildExceptionAttributes(error: unknown): Attributes | undefined {\n if (!(error instanceof Error)) {\n return undefined;\n }\n\n return sanitizeAttributes({\n 'exception.type': error.name,\n 'exception.message': error.message,\n 'exception.stacktrace': error.stack,\n });\n}\n\n@injectable()\nexport class TelemetryService implements ITelemetryService {\n private readonly contextStore = new AsyncLocalStorage<Context>();\n private readonly tracesEnabled: boolean;\n private readonly logsEnabled: boolean;\n private readonly tracerProvider?: NodeTracerProvider;\n private readonly tracer;\n private readonly logSinkReady: Promise<NodeTelemetryHandle | null>;\n\n constructor(options: TelemetryServiceOptions = {}) {\n const env = options.env ?? process.env;\n const otelDisabled = isTruthy(env.OTEL_SDK_DISABLED);\n const telemetryExplicitlyEnabled = isTruthy(env.BROWSE_TOOL_OTEL_ENABLED);\n const traceExporterConfig = resolveSignalExporterConfig(env, 'traces');\n\n this.tracesEnabled = !otelDisabled && (telemetryExplicitlyEnabled || Boolean(traceExporterConfig.url));\n this.logsEnabled = !otelDisabled;\n\n const serviceName =\n options.serviceName ?? env.BROWSE_TOOL_OTEL_SERVICE_NAME ?? env.OTEL_SERVICE_NAME ?? DEFAULT_SERVICE_NAME;\n const serviceVersion = options.serviceVersion ?? env.npm_package_version;\n\n const resourceAttributes: Attributes = {\n 'service.name': serviceName,\n };\n if (serviceVersion) {\n resourceAttributes['service.version'] = serviceVersion;\n }\n const resource = resourceFromAttributes(resourceAttributes);\n\n if (this.tracesEnabled) {\n const traceExporter = options.traceExporter ?? new OTLPTraceExporter(traceExporterConfig);\n this.tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors: [\n options.traceExporter ? new SimpleSpanProcessor(traceExporter) : new BatchSpanProcessor(traceExporter),\n ],\n });\n }\n\n this.tracer =\n this.tracerProvider?.getTracer(serviceName, serviceVersion) ?? trace.getTracer(serviceName, serviceVersion);\n this.logSinkReady = this.logsEnabled\n ? createNodeTelemetry({\n env,\n serviceName,\n serviceVersion,\n workspaceRoot: resolveWorkspaceRoot(),\n enableTraces: false,\n enableLogs: true,\n }).catch(() => null)\n : Promise.resolve(null);\n }\n\n isEnabled(): boolean {\n return this.tracesEnabled || this.logsEnabled;\n }\n\n getActiveTraceContext(): { traceId?: string; spanId?: string } {\n const activeContext = this.contextStore.getStore();\n if (!activeContext) {\n return {};\n }\n\n const activeSpan = trace.getSpan(activeContext);\n if (!activeSpan) {\n return {};\n }\n\n const spanContext = activeSpan.spanContext();\n return {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n };\n }\n\n async runInSpan<T>(\n name: string,\n options: TelemetrySpanOptions,\n callback: (span: Span | undefined) => Promise<T> | T,\n ): Promise<T> {\n if (!this.tracesEnabled) {\n return await callback(undefined);\n }\n\n const parentContext = this.contextStore.getStore() ?? context.active();\n const span = this.tracer.startSpan(\n name,\n {\n kind: options.kind ?? SpanKind.INTERNAL,\n attributes: sanitizeAttributes(options.attributes),\n },\n parentContext,\n );\n const activeContext = trace.setSpan(parentContext, span);\n\n return await this.contextStore.run(activeContext, async () => {\n try {\n return await callback(span);\n } catch (error) {\n span.recordException(error instanceof Error ? error : new Error(getErrorMessage(error)));\n span.setStatus({ code: SpanStatusCode.ERROR, message: getErrorMessage(error) });\n throw error;\n } finally {\n span.end();\n }\n });\n }\n\n log(level: TelemetryLogLevel, message: string, options: TelemetryLogOptions = {}): void {\n if (!this.logsEnabled) {\n return;\n }\n\n void this.logSinkReady.then((handle) => {\n if (!handle) {\n return;\n }\n\n const activeContext = options.context ?? this.contextStore.getStore() ?? context.active();\n const exceptionAttributes = buildExceptionAttributes(options.exception);\n handle.logger[level](message, {\n attributes: sanitizeAttributes({\n ...options.attributes,\n ...exceptionAttributes,\n }),\n context: activeContext,\n exception: options.exception,\n });\n });\n }\n\n async forceFlush(): Promise<void> {\n const sinkHandle = await this.logSinkReady;\n await Promise.allSettled([this.tracerProvider?.forceFlush(), sinkHandle?.flush()]);\n }\n\n async shutdown(): Promise<void> {\n const sinkHandle = await this.logSinkReady;\n await Promise.allSettled([this.tracerProvider?.shutdown(), sinkHandle?.shutdown()]);\n }\n}\n","/**\n * Shared timeout defaults for browse-tool execution paths.\n */\n\nexport const DEFAULT_TOOL_TIMEOUT_MS = 180_000;\n","/**\n * Extension Task Queue Service\n *\n * DESIGN PATTERNS:\n * - Service Layer Pattern for encapsulating task queue logic\n * - Promise-based async flow for task result waiting\n * - Observer pattern for connection status tracking\n *\n * CODING STANDARDS:\n * - Use typed interfaces for tasks and results\n * - Implement timeout handling for reliability\n * - Use Maps for efficient task lookup\n *\n * AVOID:\n * - Unbounded queues (implement limits)\n * - Missing timeout cleanup\n * - Memory leaks from unresolved promises\n */\n\nimport { inject, injectable, optional } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { TaskPush, TaskTelemetryContext } from '../protocol/extension-messages.js';\nimport { TelemetryService, type ITelemetryService } from './TelemetryService.js';\nimport type { WebSocketHub } from './WebSocketHub.js';\n\n/**\n * Task representing an MCP tool execution request\n */\nexport interface ExtensionTask {\n id: string;\n tool: string;\n arguments: Record<string, unknown>;\n createdAt: Date;\n timeoutMs: number;\n browserId?: string;\n pageId?: string;\n telemetry?: TaskTelemetryContext;\n}\n\n/**\n * Result from extension tool execution\n */\nexport interface ExtensionTaskResult {\n taskId: string;\n success: boolean;\n result?: {\n content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n isError?: boolean;\n };\n error?: string;\n}\n\n/**\n * Connection status for extension\n */\nexport interface ExtensionConnectionStatus {\n connected: boolean;\n lastPollAt?: Date;\n lastResultAt?: Date;\n pendingTasks: number;\n}\n\ninterface PendingTask {\n task: ExtensionTask;\n resolve: (result: ExtensionTaskResult) => void;\n reject: (error: Error) => void;\n timeoutId: NodeJS.Timeout;\n}\n\n@injectable()\nexport class ExtensionTaskQueue {\n private pendingTasks = new Map<string, PendingTask>();\n private taskQueue: ExtensionTask[] = [];\n private lastPollAt?: Date;\n private lastResultAt?: Date;\n private readonly maxQueueSize = 100;\n private readonly defaultTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS;\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.WebSocketHub) @optional() private webSocketHub?: WebSocketHub,\n @inject(PLAYWRIGHT_TYPES.TelemetryService)\n @optional()\n private telemetry: ITelemetryService = new TelemetryService(),\n ) {}\n\n /**\n * Queue a task for extension execution and wait for result.\n * Extracts browserId from args if not provided.\n * If WebSocket is available, pushes directly via WebSocket.\n * Otherwise falls back to HTTP polling queue.\n */\n async queueTask(\n tool: string,\n args: Record<string, unknown>,\n timeoutMs: number = this.defaultTimeoutMs,\n browserId?: string,\n ): Promise<ExtensionTaskResult> {\n if (this.pendingTasks.size >= this.maxQueueSize) {\n throw new Error(`Task queue full (max ${this.maxQueueSize} tasks)`);\n }\n\n const effectiveBrowserId = browserId ?? (args.browserId as string | undefined);\n\n const task: ExtensionTask = {\n id: crypto.randomUUID(),\n tool,\n arguments: args,\n createdAt: new Date(),\n timeoutMs,\n browserId: effectiveBrowserId,\n pageId: typeof args.pageId === 'string' ? args.pageId : undefined,\n telemetry: this.getTaskTelemetryContext(),\n };\n\n return new Promise<ExtensionTaskResult>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingTasks.delete(task.id);\n const queueIndex = this.taskQueue.findIndex((t) => t.id === task.id);\n if (queueIndex !== -1) {\n this.taskQueue.splice(queueIndex, 1);\n }\n reject(new Error(`Task ${task.id} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n this.pendingTasks.set(task.id, {\n task,\n resolve,\n reject,\n timeoutId,\n });\n\n // Try to push via WebSocket if available\n if (this.webSocketHub) {\n // First try the specific browserId if provided\n let pushed = effectiveBrowserId ? this.pushTaskViaWebSocket(task, effectiveBrowserId) : false;\n\n // If specific browser not connected, try any available connection\n if (!pushed) {\n pushed = this.pushTaskToAnyConnection(task);\n }\n\n if (pushed) {\n return;\n }\n }\n\n // Fallback to HTTP polling queue\n this.taskQueue.push(task);\n });\n }\n\n /**\n * Push a task to any available WebSocket connection.\n * Used when no specific browserId is provided.\n */\n private pushTaskToAnyConnection(task: ExtensionTask): boolean {\n if (!this.webSocketHub) {\n return false;\n }\n\n const stats = this.webSocketHub.getStats();\n if (stats.totalConnections === 0) {\n return false;\n }\n\n const firstConnection = stats.connections[0];\n if (!firstConnection) {\n return false;\n }\n\n return this.pushTaskViaWebSocket(task, firstConnection.browserId);\n }\n\n /**\n * Push a task directly to the extension via WebSocket.\n * Returns true if successfully pushed, false if no WebSocket connection available.\n */\n private pushTaskViaWebSocket(task: ExtensionTask, browserId: string): boolean {\n if (!this.webSocketHub) {\n return false;\n }\n\n const taskPush: TaskPush = {\n type: 'task:push',\n id: crypto.randomUUID(),\n payload: {\n taskId: task.id,\n tool: task.tool,\n arguments: task.arguments,\n telemetry: task.telemetry,\n },\n };\n\n return this.webSocketHub.pushTask(browserId, taskPush);\n }\n\n /**\n * Get next task for extension to execute\n */\n getNextTask(): ExtensionTask | undefined {\n this.lastPollAt = new Date();\n return this.taskQueue.shift();\n }\n\n /**\n * Submit result from extension execution\n */\n submitResult(result: ExtensionTaskResult): ExtensionTask | undefined {\n this.lastResultAt = new Date();\n\n const pending = this.pendingTasks.get(result.taskId);\n if (!pending) {\n return undefined;\n }\n\n clearTimeout(pending.timeoutId);\n this.pendingTasks.delete(result.taskId);\n pending.resolve(result);\n return pending.task;\n }\n\n /**\n * Get connection status.\n * Checks both WebSocket connections and HTTP polling activity.\n */\n getConnectionStatus(): ExtensionConnectionStatus {\n const now = new Date();\n const pollThresholdMs = 5000;\n\n // Check HTTP polling connection\n const httpConnected = this.lastPollAt ? now.getTime() - this.lastPollAt.getTime() < pollThresholdMs : false;\n\n // Check WebSocket connections\n const wsConnected = this.webSocketHub ? this.webSocketHub.getStats().totalConnections > 0 : false;\n\n return {\n connected: httpConnected || wsConnected,\n lastPollAt: this.lastPollAt,\n lastResultAt: this.lastResultAt,\n pendingTasks: this.pendingTasks.size,\n };\n }\n\n /**\n * Clear all pending tasks with error\n */\n clearAllTasks(reason: string): void {\n for (const [id, pending] of this.pendingTasks) {\n clearTimeout(pending.timeoutId);\n pending.reject(new Error(reason));\n this.pendingTasks.delete(id);\n }\n this.taskQueue.length = 0;\n }\n\n get queueSize(): number {\n return this.taskQueue.length;\n }\n\n get pendingCount(): number {\n return this.pendingTasks.size;\n }\n\n private getTaskTelemetryContext(): TaskTelemetryContext | undefined {\n const traceContext = this.telemetry.getActiveTraceContext();\n if (!traceContext.traceId && !traceContext.spanId) {\n return undefined;\n }\n\n return {\n traceId: traceContext.traceId,\n parentSpanId: traceContext.spanId,\n };\n }\n}\n","/**\n * Extension Tool Delegator Service\n *\n * DESIGN PATTERNS:\n * - Service Layer Pattern for tool execution delegation\n * - Dependency Injection with InversifyJS\n * - Strategy pattern for extension vs local execution\n *\n * CODING STANDARDS:\n * - Use typed interfaces for tool results\n * - Implement graceful fallback for unsupported tools\n * - Track tool support status\n *\n * AVOID:\n * - Hardcoded tool lists (use configuration)\n * - Missing error handling for delegation failures\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ExtensionTaskQueue } from './ExtensionTaskQueue.js';\n\n/**\n * Tools supported by the Chrome extension\n */\nconst EXTENSION_SUPPORTED_TOOLS = new Set([\n 'browser_navigate',\n 'browser_go_back',\n 'browser_go_forward',\n 'browser_reload',\n 'browser_click',\n 'browser_fill',\n 'browser_type',\n 'browser_upload_file',\n 'browser_select',\n 'browser_hover',\n 'browser_drag',\n 'browser_press_key',\n 'browser_pdf',\n 'browser_screenshot',\n 'browser_snapshot',\n 'browser_list_pages',\n 'browser_new_page',\n 'browser_select_page',\n 'browser_close_page',\n 'browser_wait_for',\n 'browser_evaluate_script',\n 'browser_resize_page',\n 'browser_handle_dialog',\n 'browser_emulate',\n 'browser_list_network_requests',\n 'browser_get_network_request',\n 'browser_list_console_messages',\n 'browser_expect',\n]);\n\n/**\n * Tools not applicable in extension mode\n */\nconst EXTENSION_NOT_APPLICABLE_TOOLS = new Set([\n 'browser_launch',\n 'browser_list_profiles',\n 'browser_create_profile',\n 'browser_delete_profile',\n 'browser_save_profile_state',\n 'browser_start_trace',\n 'browser_stop_trace',\n 'run_spec',\n 'discover_specs',\n]);\n\n@injectable()\nexport class ExtensionToolDelegator {\n constructor(\n @inject(PLAYWRIGHT_TYPES.ExtensionTaskQueue)\n private taskQueue: ExtensionTaskQueue,\n ) {}\n\n /**\n * Check if a tool is supported by the extension\n */\n isToolSupported(toolName: string): boolean {\n return EXTENSION_SUPPORTED_TOOLS.has(toolName);\n }\n\n /**\n * Check if a tool is not applicable in extension mode\n */\n isToolNotApplicable(toolName: string): boolean {\n return EXTENSION_NOT_APPLICABLE_TOOLS.has(toolName);\n }\n\n /**\n * Delegate tool execution to the Chrome extension\n */\n async executeTool(toolName: string, args: Record<string, unknown>): Promise<CallToolResult> {\n if (this.isToolNotApplicable(toolName)) {\n return {\n content: [\n {\n type: 'text',\n text: `Tool \"${toolName}\" is not available in Chrome extension mode. This tool requires Playwright and cannot be run through the extension.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!this.isToolSupported(toolName)) {\n return {\n content: [\n {\n type: 'text',\n text: `Tool \"${toolName}\" is not yet implemented in Chrome extension mode.`,\n },\n ],\n isError: true,\n };\n }\n\n const status = this.taskQueue.getConnectionStatus();\n if (!status.connected) {\n return {\n content: [\n {\n type: 'text',\n text: 'Chrome extension is not connected. Please ensure the extension is running and connected to the server.',\n },\n ],\n isError: true,\n };\n }\n\n try {\n const result = await this.taskQueue.queueTask(toolName, args);\n\n if (!result.success) {\n // Error message can be in result.error or in result.result.content[0].text\n const errorText =\n result.error ||\n (result.result?.content?.[0] as { text?: string })?.text ||\n 'Unknown error from Chrome extension';\n return {\n content: [\n {\n type: 'text',\n text: errorText,\n },\n ],\n isError: true,\n };\n }\n\n if (result.result) {\n return result.result as CallToolResult;\n }\n return {\n content: [{ type: 'text' as const, text: 'Tool executed successfully' }],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: error instanceof Error ? error.message : String(error),\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * Get list of supported tools\n */\n getSupportedTools(): string[] {\n return Array.from(EXTENSION_SUPPORTED_TOOLS);\n }\n\n /**\n * Get list of not applicable tools\n */\n getNotApplicableTools(): string[] {\n return Array.from(EXTENSION_NOT_APPLICABLE_TOOLS);\n }\n}\n","/**\n * AutomationRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for spec file execution\n * - Dependency injection via InversifyJS\n * - Single responsibility: spec execution management\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Sessions track all pages created during spec execution\n *\n * AVOID:\n * - Global state for automation sessions\n */\n\nimport 'reflect-metadata/lite';\nimport { dirname } from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport type { Browser, BrowserContext, CDPSession, Page } from 'playwright';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { TestFilter } from '../types/index.js';\nimport type { IBrowserService, LaunchOptions, VideoRecordingOptions } from './BrowserService.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport type { ISetupRunner, SetupResult } from './SetupRunner.js';\nimport type { ISpecMetadataService } from './SpecMetadataService.js';\nimport type { ISpecRunner, SpecExecutionResult } from './SpecRunner.js';\nimport type { IWebServerManager, ServerStartResult } from './WebServerManager.js';\n\n/**\n * Status of a spec session.\n */\nexport type AutomationStatus = 'pending' | 'running' | 'completed' | 'failed';\n\n/**\n * Preset window position names for common layouts.\n */\nexport type WindowPositionPreset = 'right-half' | 'left-half' | 'top-half' | 'bottom-half' | 'center' | 'fullscreen';\n\n/**\n * Screen dimensions for calculating window bounds.\n */\nexport interface ScreenDimensions {\n width: number;\n height: number;\n}\n\n/**\n * Window bounds configuration.\n */\nexport interface WindowBounds {\n /** Preset position name or custom bounds */\n preset?: WindowPositionPreset;\n /** Custom x position (overrides preset) */\n x?: number;\n /** Custom y position (overrides preset) */\n y?: number;\n /** Custom width (overrides preset) */\n width?: number;\n /** Custom height (overrides preset) */\n height?: number;\n /** Screen dimensions for preset calculations (defaults to 1920x1080) */\n screenDimensions?: ScreenDimensions;\n}\n\n/** Default screen dimensions for window positioning */\nconst DEFAULT_SCREEN: ScreenDimensions = { width: 1920, height: 1080 };\n\n/**\n * Represents a spec session tracking execution state.\n */\nexport interface AutomationRunSession {\n /** Unique session ID */\n id: string;\n /** Spec file path */\n scriptPath: string;\n /** Current status */\n status: AutomationStatus;\n /** All page IDs created during this session */\n pageIds: string[];\n /** Browser instance ID */\n browserId: string | null;\n /** Error message if failed */\n error: string | null;\n /** Creation timestamp */\n createdAt: Date;\n /** Last updated timestamp */\n updatedAt: Date;\n}\n\n/** Browser execution mode for spec running - only playwright mode supports spec execution */\nexport type SpecBrowserMode = 'playwright';\n\n/**\n * Options for running a spec file.\n */\nexport interface RunSpecOptions {\n /** Spec file path to execute */\n specPath: string;\n /** Optional session ID (auto-generated if not provided) */\n automationId?: string;\n /** Keep browser open after spec execution completes */\n keepBrowserOpen?: boolean;\n /** Browser launch options */\n browserOptions?: LaunchOptions;\n /** Browser execution mode */\n mode?: SpecBrowserMode;\n /** Video recording options. Auto-enabled for extension mode */\n recordVideo?: VideoRecordingOptions;\n /** Existing browser ID to use instead of launching new browser */\n browserId?: string;\n /** Existing page ID to use instead of creating new page */\n pageId?: string;\n /** Window bounds configuration for positioning/sizing */\n windowBounds?: WindowBounds;\n /** Hooks file path to bundle with the spec for env setup */\n hooksPath?: string;\n}\n\n/**\n * Enhanced options for running a spec file with filtering and arguments.\n */\nexport interface RunSpecOptionsEnhanced extends RunSpecOptions {\n /** Filter to select which tests to run */\n testFilter?: TestFilter;\n /** Arguments to pass to the spec */\n specArgs?: Record<string, unknown>;\n /** Path to playwright.config.ts for loading MCP config */\n configPath?: string;\n /** Run setup file before spec execution */\n runSetup?: boolean;\n /** Start web server before spec execution */\n startServer?: boolean;\n /** Stop web server after spec execution */\n stopServerAfter?: boolean;\n}\n\n/**\n * Enhanced result of running a spec file with server and setup status.\n */\nexport interface RunSpecResultEnhanced extends RunSpecResult {\n /** Server start result if startServer was true */\n serverResult?: ServerStartResult;\n /** Setup execution result if runSetup was true */\n setupResult?: SetupResult;\n}\n\n/**\n * Result of running a spec file.\n */\nexport interface RunSpecResult {\n /** Session ID */\n automationId: string;\n /** Status after execution */\n status: AutomationStatus;\n /** Page IDs available for interaction */\n pageIds: string[];\n /** Browser instance ID */\n browserId: string | null;\n /** Spec execution result */\n specResult?: SpecExecutionResult;\n}\n\nexport interface IAutomationRunner {\n /**\n * Runs a Playwright spec file.\n */\n runSpec(options: RunSpecOptions): Promise<RunSpecResult>;\n\n /**\n * Runs a Playwright spec file with filtering and enhanced options.\n * Orchestrates server start, setup execution, and spec run.\n */\n runSpecEnhanced(options: RunSpecOptionsEnhanced): Promise<RunSpecResultEnhanced>;\n\n /**\n * Gets a session by ID.\n */\n getSession(automationId: string): AutomationRunSession | undefined;\n\n /**\n * Lists all sessions.\n */\n listSessions(): AutomationRunSession[];\n\n /**\n * Stops a session.\n */\n stop(automationId: string): Promise<boolean>;\n}\n\n@injectable()\nexport class AutomationRunner implements IAutomationRunner {\n private sessions: Map<string, AutomationRunSession> = new Map();\n private sessionIdCounter = 0;\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.BrowserService) private browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.SpecRunner) private specRunner: ISpecRunner,\n @inject(PLAYWRIGHT_TYPES.SpecMetadataService) private specMetadataService: ISpecMetadataService,\n @inject(PLAYWRIGHT_TYPES.SetupRunner) private setupRunner: ISetupRunner,\n @inject(PLAYWRIGHT_TYPES.WebServerManager) private webServerManager: IWebServerManager,\n @inject(PLAYWRIGHT_TYPES.PageRegistry) private pageRegistry: IPageRegistry,\n ) {}\n\n /**\n * Runs a Playwright spec file.\n */\n async runSpec(options: RunSpecOptions): Promise<RunSpecResult> {\n const {\n specPath,\n automationId: providedId,\n keepBrowserOpen = false,\n browserOptions = {},\n recordVideo,\n browserId: existingBrowserId,\n pageId: existingPageId,\n windowBounds,\n hooksPath,\n } = options;\n\n const automationId = providedId ?? `spec-${++this.sessionIdCounter}`;\n\n const session: AutomationRunSession = {\n id: automationId,\n scriptPath: specPath,\n status: 'pending',\n pageIds: [],\n browserId: null,\n error: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n\n this.sessions.set(automationId, session);\n\n try {\n let page: Page;\n let context: BrowserContext;\n let browser: Browser;\n let isExistingPage = false;\n\n if (existingBrowserId && existingPageId) {\n const pageEntry = this.pageRegistry.get(existingPageId);\n if (!pageEntry) {\n throw new Error(`Page with ID ${existingPageId} not found`);\n }\n if (!pageEntry.page || !pageEntry.context || !pageEntry.browser) {\n throw new Error('Page entry is missing required Playwright instances');\n }\n page = pageEntry.page;\n context = pageEntry.context;\n browser = pageEntry.browser;\n session.browserId = existingBrowserId;\n session.pageIds.push(existingPageId);\n isExistingPage = true;\n } else {\n const launchResult = await this.browserService.launch({\n ...browserOptions,\n recordVideo,\n onPageClose: (pageId) => {\n const idx = session.pageIds.indexOf(pageId);\n if (idx >= 0) {\n session.pageIds.splice(idx, 1);\n }\n },\n });\n\n session.browserId = launchResult.browserInstance.id;\n session.pageIds.push(launchResult.pageId);\n launchResult.browserInstance.specOrigin = true;\n\n const { context: ctx, browser: br } = launchResult.browserInstance;\n if (!ctx || !br) {\n throw new Error('Browser context or browser is not available');\n }\n page = launchResult.page;\n context = ctx;\n browser = br;\n }\n\n session.status = 'running';\n session.updatedAt = new Date();\n\n if (windowBounds) {\n await this.applyWindowBounds(browser, windowBounds);\n }\n\n const specResult = await this.specRunner.executeSpec({\n specPath,\n sessionId: automationId,\n page,\n context,\n browser,\n hooksPath,\n baseURL: browserOptions.baseURL,\n });\n\n session.status = 'completed';\n session.updatedAt = new Date();\n\n const shouldCloseBrowser = !keepBrowserOpen && !isExistingPage && session.browserId;\n if (shouldCloseBrowser && session.browserId) {\n await this.browserService.closeBrowser(session.browserId);\n }\n\n return {\n automationId,\n status: 'completed',\n pageIds: keepBrowserOpen || isExistingPage ? [...session.pageIds] : [],\n browserId: keepBrowserOpen || isExistingPage ? session.browserId : null,\n specResult,\n };\n } catch (error) {\n session.status = 'failed';\n const errorMessage = error instanceof Error ? error.message : String(error);\n session.error = errorMessage;\n session.updatedAt = new Date();\n\n const isExistingPage = !!(existingBrowserId && existingPageId);\n if (!keepBrowserOpen && !isExistingPage && session.browserId) {\n await this.browserService.closeBrowser(session.browserId);\n }\n\n return {\n automationId,\n status: 'failed',\n pageIds: keepBrowserOpen || isExistingPage ? [...session.pageIds] : [],\n browserId: keepBrowserOpen || isExistingPage ? session.browserId : null,\n };\n }\n }\n\n /**\n * Runs a Playwright spec file with filtering and enhanced options.\n * Orchestrates server start, setup execution, and spec run.\n */\n async runSpecEnhanced(options: RunSpecOptionsEnhanced): Promise<RunSpecResultEnhanced> {\n const {\n specPath,\n automationId: providedId,\n keepBrowserOpen = false,\n browserOptions = {},\n recordVideo,\n testFilter,\n specArgs,\n configPath,\n runSetup = false,\n startServer = false,\n stopServerAfter = true,\n browserId: existingBrowserId,\n pageId: existingPageId,\n windowBounds,\n hooksPath,\n } = options;\n\n const automationId = providedId ?? `spec-${++this.sessionIdCounter}`;\n\n const session: AutomationRunSession = {\n id: automationId,\n scriptPath: specPath,\n status: 'pending',\n pageIds: [],\n browserId: null,\n error: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n\n this.sessions.set(automationId, session);\n\n let serverResult: ServerStartResult | undefined;\n let setupResult: SetupResult | undefined;\n\n try {\n const config = configPath ? await this.specMetadataService.loadPlaywrightConfig(configPath) : null;\n const configDir = configPath ? dirname(configPath) : process.cwd();\n\n if (startServer && config?.webServer) {\n serverResult = await this.webServerManager.startServer(config.webServer, configDir);\n }\n\n let page: Page;\n let context: BrowserContext;\n let browser: Browser;\n let isExistingPage = false;\n\n if (existingBrowserId && existingPageId) {\n const pageEntry = this.pageRegistry.get(existingPageId);\n if (!pageEntry) {\n throw new Error(`Page with ID ${existingPageId} not found`);\n }\n if (!pageEntry.page || !pageEntry.context || !pageEntry.browser) {\n throw new Error('Page entry is missing required Playwright instances');\n }\n page = pageEntry.page;\n context = pageEntry.context;\n browser = pageEntry.browser;\n session.browserId = existingBrowserId;\n session.pageIds.push(existingPageId);\n isExistingPage = true;\n } else {\n const launchResult = await this.browserService.launch({\n ...browserOptions,\n recordVideo,\n onPageClose: (pageId) => {\n const idx = session.pageIds.indexOf(pageId);\n if (idx >= 0) {\n session.pageIds.splice(idx, 1);\n }\n },\n });\n\n session.browserId = launchResult.browserInstance.id;\n session.pageIds.push(launchResult.pageId);\n launchResult.browserInstance.specOrigin = true;\n\n const { context: ctx, browser: br } = launchResult.browserInstance;\n if (!ctx) {\n throw new Error('Browser context is not available');\n }\n if (!br) {\n throw new Error('Browser instance is not available');\n }\n page = launchResult.page;\n context = ctx;\n browser = br;\n }\n\n session.status = 'running';\n session.updatedAt = new Date();\n\n if (windowBounds) {\n await this.applyWindowBounds(browser, windowBounds);\n }\n\n if (runSetup && config?.setupFile) {\n const setupPath = config.setupFile.startsWith('/') ? config.setupFile : `${configDir}/${config.setupFile}`;\n const setupExport = config.setupExport ?? 'default';\n\n setupResult = await this.setupRunner.runSetup(setupPath, setupExport, {\n browser,\n context,\n page,\n });\n\n if (!setupResult.success) {\n throw new Error(setupResult.error ?? 'Setup failed');\n }\n }\n\n const specResult = await this.specRunner.executeSpecEnhanced({\n specPath,\n sessionId: automationId,\n page,\n context,\n browser,\n testFilter,\n specArgs: { ...specArgs, ...setupResult?.state },\n hooksPath,\n baseURL: browserOptions.baseURL,\n });\n\n session.status = 'completed';\n session.updatedAt = new Date();\n\n if (stopServerAfter && serverResult?.started) {\n await this.webServerManager.stopServer();\n }\n\n if (!keepBrowserOpen && !isExistingPage && session.browserId) {\n await this.browserService.closeBrowser(session.browserId);\n }\n\n return {\n automationId,\n status: 'completed',\n pageIds: keepBrowserOpen || isExistingPage ? [...session.pageIds] : [],\n browserId: keepBrowserOpen || isExistingPage ? session.browserId : null,\n specResult,\n serverResult,\n setupResult,\n };\n } catch (error) {\n session.status = 'failed';\n session.error = error instanceof Error ? error.message : String(error);\n session.updatedAt = new Date();\n\n if (stopServerAfter && serverResult?.started) {\n await this.webServerManager.stopServer();\n }\n\n const isExistingPage = !!(existingBrowserId && existingPageId);\n if (!keepBrowserOpen && !isExistingPage && session.browserId) {\n await this.browserService.closeBrowser(session.browserId);\n }\n\n return {\n automationId,\n status: 'failed',\n pageIds: keepBrowserOpen || isExistingPage ? [...session.pageIds] : [],\n browserId: keepBrowserOpen || isExistingPage ? session.browserId : null,\n serverResult,\n setupResult,\n };\n }\n }\n\n /**\n * Gets a session by ID.\n */\n getSession(automationId: string): AutomationRunSession | undefined {\n return this.sessions.get(automationId);\n }\n\n /**\n * Lists all sessions.\n */\n listSessions(): AutomationRunSession[] {\n return Array.from(this.sessions.values());\n }\n\n /**\n * Stops a session.\n */\n async stop(automationId: string): Promise<boolean> {\n const session = this.sessions.get(automationId);\n if (!session) {\n return false;\n }\n\n if (session.browserId) {\n await this.browserService.closeBrowser(session.browserId);\n }\n\n session.status = 'completed';\n session.updatedAt = new Date();\n\n return true;\n }\n\n /**\n * Applies window bounds to the browser using CDP.\n */\n private async applyWindowBounds(browser: Browser, windowBounds: WindowBounds): Promise<void> {\n const bounds = this.resolveWindowBounds(windowBounds);\n const contexts = browser.contexts();\n if (contexts.length === 0) {\n return;\n }\n\n const pages = contexts[0].pages();\n if (pages.length === 0) {\n return;\n }\n\n let cdpSession: CDPSession | undefined;\n try {\n cdpSession = await pages[0].context().newCDPSession(pages[0]);\n const { windowId } = await cdpSession.send('Browser.getWindowForTarget');\n await cdpSession.send('Browser.setWindowBounds', {\n windowId,\n bounds: {\n left: bounds.x,\n top: bounds.y,\n width: bounds.width,\n height: bounds.height,\n },\n });\n } finally {\n if (cdpSession) {\n await cdpSession.detach();\n }\n }\n }\n\n /**\n * Resolves window bounds from preset or custom values.\n */\n private resolveWindowBounds(windowBounds: WindowBounds): { x: number; y: number; width: number; height: number } {\n const screen = windowBounds.screenDimensions ?? DEFAULT_SCREEN;\n\n if (windowBounds.preset) {\n const presetBounds = this.calculatePresetBounds(windowBounds.preset, screen);\n return {\n x: windowBounds.x ?? presetBounds.x,\n y: windowBounds.y ?? presetBounds.y,\n width: windowBounds.width ?? presetBounds.width,\n height: windowBounds.height ?? presetBounds.height,\n };\n }\n\n return {\n x: windowBounds.x ?? 0,\n y: windowBounds.y ?? 0,\n width: windowBounds.width ?? screen.width,\n height: windowBounds.height ?? screen.height,\n };\n }\n\n /**\n * Calculates bounds for preset window positions.\n */\n private calculatePresetBounds(\n preset: WindowPositionPreset,\n screen: ScreenDimensions,\n ): { x: number; y: number; width: number; height: number } {\n const halfWidth = Math.floor(screen.width / 2);\n const halfHeight = Math.floor(screen.height / 2);\n\n switch (preset) {\n case 'right-half':\n return { x: halfWidth, y: 0, width: halfWidth, height: screen.height };\n case 'left-half':\n return { x: 0, y: 0, width: halfWidth, height: screen.height };\n case 'top-half':\n return { x: 0, y: 0, width: screen.width, height: halfHeight };\n case 'bottom-half':\n return { x: 0, y: halfHeight, width: screen.width, height: halfHeight };\n case 'center': {\n const centerWidth = Math.floor(screen.width * 0.75);\n const centerHeight = Math.floor(screen.height * 0.75);\n return {\n x: Math.floor((screen.width - centerWidth) / 2),\n y: Math.floor((screen.height - centerHeight) / 2),\n width: centerWidth,\n height: centerHeight,\n };\n }\n case 'fullscreen':\n return { x: 0, y: 0, width: screen.width, height: screen.height };\n default:\n return { x: 0, y: 0, width: screen.width, height: screen.height };\n }\n }\n}\n","/**\n * BrowserLockManager\n *\n * DESIGN PATTERNS:\n * - Service pattern for browser lock detection and cleanup\n * - Platform-aware lock file handling (macOS/Linux/Windows)\n * - Orphaned process detection and cleanup\n *\n * CODING STANDARDS:\n * - Use async/await for file system operations\n * - Throw descriptive errors for lock conflicts\n * - Keep methods focused on lock management\n *\n * AVOID:\n * - Forcefully killing active browser sessions without user consent\n * - Ignoring lock files that indicate legitimate active sessions\n */\n\nimport 'reflect-metadata/lite';\nimport fs from 'node:fs';\nimport { readFile, unlink } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { injectable } from 'inversify';\n\nexport interface LockInfo {\n /** Whether a lock exists */\n isLocked: boolean;\n /** Process ID holding the lock (if detectable) */\n pid?: number;\n /** Whether the locking process is still running */\n isProcessAlive?: boolean;\n /** Lock file path */\n lockFilePath?: string;\n /** Socket file path (Linux/macOS) */\n socketPath?: string;\n}\n\nexport interface IBrowserLockManager {\n /**\n * Check if a user data directory is locked by another Chrome process\n */\n checkLock(userDataDir: string): Promise<LockInfo>;\n\n /**\n * Clean up orphaned locks from crashed processes\n * Returns true if cleanup was successful\n */\n cleanupOrphanedLocks(userDataDir: string): Promise<boolean>;\n\n /**\n * Ensure the user data directory is available for use\n * Throws an error if locked by an active process\n */\n ensureAvailable(userDataDir: string): Promise<void>;\n}\n\n/**\n * Custom error for browser session conflicts\n */\nexport class BrowserSessionConflictError extends Error {\n constructor(\n message: string,\n public readonly pid: number | undefined,\n public readonly userDataDir: string,\n ) {\n super(message);\n this.name = 'BrowserSessionConflictError';\n }\n}\n\n@injectable()\nexport class BrowserLockManager implements IBrowserLockManager {\n private readonly platform = os.platform();\n\n /**\n * Check if a user data directory is locked by another Chrome process\n */\n async checkLock(userDataDir: string): Promise<LockInfo> {\n const lockInfo: LockInfo = {\n isLocked: false,\n };\n\n if (this.platform === 'win32') {\n return this.checkWindowsLock(userDataDir, lockInfo);\n }\n\n return this.checkUnixLock(userDataDir, lockInfo);\n }\n\n /**\n * Clean up orphaned locks from crashed processes\n */\n async cleanupOrphanedLocks(userDataDir: string): Promise<boolean> {\n const lockInfo = await this.checkLock(userDataDir);\n\n if (!lockInfo.isLocked) {\n return true;\n }\n\n if (lockInfo.isProcessAlive) {\n return false;\n }\n\n // Process is dead, clean up orphaned locks\n const filesToClean = [\n path.join(userDataDir, 'SingletonLock'),\n path.join(userDataDir, 'SingletonCookie'),\n path.join(userDataDir, 'SingletonSocket'),\n path.join(userDataDir, 'lockfile'),\n ];\n\n for (const file of filesToClean) {\n try {\n if (fs.existsSync(file)) {\n await unlink(file);\n }\n } catch {\n // Ignore errors during cleanup\n }\n }\n\n // Also clean up the Default/LOCK file if it exists\n const defaultLockPath = path.join(userDataDir, 'Default', 'LOCK');\n try {\n if (fs.existsSync(defaultLockPath)) {\n await unlink(defaultLockPath);\n }\n } catch {\n // Ignore\n }\n\n return true;\n }\n\n /**\n * Ensure the user data directory is available for use\n */\n async ensureAvailable(userDataDir: string): Promise<void> {\n if (!fs.existsSync(userDataDir)) {\n return;\n }\n\n const lockInfo = await this.checkLock(userDataDir);\n\n if (!lockInfo.isLocked) {\n return;\n }\n\n if (lockInfo.isProcessAlive) {\n throw new BrowserSessionConflictError(\n `Browser session conflict: Another Chrome process (PID: ${lockInfo.pid}) is using this profile.\\nUser data directory: ${userDataDir}\\n\\nTo resolve:\\n1. Close the existing browser session manually\\n2. Or use a different profile name\\n3. Or kill the process: kill ${lockInfo.pid}`,\n lockInfo.pid,\n userDataDir,\n );\n }\n\n const cleaned = await this.cleanupOrphanedLocks(userDataDir);\n if (!cleaned) {\n throw new Error(`Failed to clean up orphaned browser lock in ${userDataDir}`);\n }\n }\n\n /**\n * Check Unix-style lock files (macOS/Linux)\n * Note: Chrome creates SingletonLock as a symlink where the TARGET string\n * contains the lock info (hostname-PID), not pointing to an actual file.\n * We must use lstatSync (not existsSync) to detect the symlink itself.\n */\n private async checkUnixLock(userDataDir: string, lockInfo: LockInfo): Promise<LockInfo> {\n const singletonLockPath = path.join(userDataDir, 'SingletonLock');\n const singletonSocketPath = path.join(userDataDir, 'SingletonSocket');\n\n lockInfo.lockFilePath = singletonLockPath;\n lockInfo.socketPath = singletonSocketPath;\n\n // Check if SingletonLock symlink exists (don't follow the link)\n if (this.symlinkExists(singletonLockPath)) {\n try {\n const lockTarget = fs.readlinkSync(singletonLockPath);\n // Format: hostname-PID\n const match = lockTarget.match(/-(\\d+)$/);\n if (match) {\n lockInfo.isLocked = true;\n lockInfo.pid = Number.parseInt(match[1], 10);\n lockInfo.isProcessAlive = this.isProcessRunning(lockInfo.pid);\n }\n } catch {\n lockInfo.isLocked = true;\n lockInfo.isProcessAlive = fs.existsSync(singletonSocketPath);\n }\n }\n\n if (!lockInfo.isLocked && fs.existsSync(singletonSocketPath)) {\n lockInfo.isLocked = true;\n lockInfo.isProcessAlive = true;\n }\n\n return lockInfo;\n }\n\n /**\n * Check if a symlink exists (without following it)\n */\n private symlinkExists(symlinkPath: string): boolean {\n try {\n const stats = fs.lstatSync(symlinkPath);\n return stats.isSymbolicLink();\n } catch {\n return false;\n }\n }\n\n /**\n * Check Windows-style lock files\n */\n private async checkWindowsLock(userDataDir: string, lockInfo: LockInfo): Promise<LockInfo> {\n const lockFilePath = path.join(userDataDir, 'lockfile');\n\n lockInfo.lockFilePath = lockFilePath;\n\n if (fs.existsSync(lockFilePath)) {\n try {\n const content = await readFile(lockFilePath, 'utf-8');\n const pid = Number.parseInt(content.trim(), 10);\n if (!Number.isNaN(pid)) {\n lockInfo.isLocked = true;\n lockInfo.pid = pid;\n lockInfo.isProcessAlive = this.isProcessRunning(pid);\n }\n } catch {\n lockInfo.isLocked = true;\n lockInfo.isProcessAlive = true;\n }\n }\n\n return lockInfo;\n }\n\n /**\n * Check if a process is running by PID\n */\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n return err.code === 'EPERM';\n }\n }\n}\n","{\n \"version\": \"147.0.7727.3\",\n \"dockerImage\": \"browse-tool/chrome-for-testing:147.0.7727.3\",\n \"dockerBrowserBinary\": \"/usr/local/bin/google-chrome-for-testing\",\n \"dockerPlatform\": \"linux/amd64\"\n}\n","/**\n * ChromeForTestingService\n *\n * DESIGN PATTERNS:\n * - Service pattern for Chrome for Testing lifecycle management\n * - Downloads and caches Chrome for Testing binary\n * - Provides executable path for extension mode\n *\n * CODING STANDARDS:\n * - Use async/await for I/O operations\n * - Proper error handling with try-catch\n * - Clear responsibility: manage Chrome for Testing lifecycle\n *\n * AVOID:\n * - Synchronous blocking operations\n * - Mixing concerns with browser launching\n */\n\nimport 'reflect-metadata/lite';\nimport { type ChildProcess, spawn } from 'node:child_process';\nimport { createWriteStream, existsSync, mkdirSync } from 'node:fs';\nimport { chmod, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport { arch, homedir, platform } from 'node:os';\nimport { join } from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { injectable } from 'inversify';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\n\n/** Chrome for Testing version info from API */\ninterface CftVersionInfo {\n version: string;\n revision: string;\n downloads: {\n chrome: Array<{\n platform: string;\n url: string;\n }>;\n };\n}\n\n/** Chrome for Testing API response */\ninterface CftApiResponse {\n timestamp: string;\n versions: CftVersionInfo[];\n}\n\nexport const PINNED_CHROME_FOR_TESTING_VERSION = chromeForTestingConfig.version;\nexport const CHROME_FOR_TESTING_VERSION_ENV_VAR = 'BROWSE_TOOL_CHROME_FOR_TESTING_VERSION';\nexport const RECORDER_PAGE_CLEANUP_SAFE_MIN_VERSION = '147.0.7727.3';\n\nfunction compareChromeVersionParts(left: string, right: string): number {\n const leftParts = left.split('.').map((part) => Number.parseInt(part, 10) || 0);\n const rightParts = right.split('.').map((part) => Number.parseInt(part, 10) || 0);\n const maxLength = Math.max(leftParts.length, rightParts.length);\n\n for (let index = 0; index < maxLength; index += 1) {\n const leftPart = leftParts[index] ?? 0;\n const rightPart = rightParts[index] ?? 0;\n if (leftPart !== rightPart) {\n return leftPart > rightPart ? 1 : -1;\n }\n }\n\n return 0;\n}\n\nexport function getRequestedChromeForTestingVersion(): string {\n const override = process.env[CHROME_FOR_TESTING_VERSION_ENV_VAR]?.trim();\n return override && override.length > 0 ? override : PINNED_CHROME_FOR_TESTING_VERSION;\n}\n\nexport function isRecorderPageCleanupSafeChromeVersion(version: string): boolean {\n return compareChromeVersionParts(version, RECORDER_PAGE_CLEANUP_SAFE_MIN_VERSION) >= 0;\n}\n\nexport function shouldCleanupRecorderPageAfterStop(): boolean {\n return isRecorderPageCleanupSafeChromeVersion(getRequestedChromeForTestingVersion());\n}\n\nexport interface IChromeForTestingService {\n /** Get the path to Chrome for Testing executable, downloading if necessary */\n getExecutablePath(): Promise<string>;\n /** Check if Chrome for Testing is installed */\n isInstalled(): Promise<boolean>;\n /** Get the installed version */\n getInstalledVersion(): Promise<string | null>;\n /** Force re-download of Chrome for Testing */\n update(): Promise<string>;\n}\n\n@injectable()\nexport class ChromeForTestingService implements IChromeForTestingService {\n private static readonly API_URL =\n 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json';\n\n /** Cache directory for Chrome for Testing */\n private readonly cacheDir: string;\n\n constructor() {\n this.cacheDir = join(homedir(), '.cache', 'chrome-for-testing');\n }\n\n /**\n * Get the path to Chrome for Testing executable.\n * Refreshes to the pinned known-good build when the cache is stale.\n */\n async getExecutablePath(): Promise<string> {\n const execPath = this.getExpectedExecutablePath();\n const installedVersion = await this.getInstalledVersion();\n\n try {\n const requestedVersion = this.getRequestedVersion();\n const pinnedVersion = await this.fetchVersionInfo(requestedVersion);\n const isCurrentInstall = existsSync(execPath) && installedVersion === requestedVersion;\n if (isCurrentInstall) {\n return execPath;\n }\n\n return this.download(pinnedVersion);\n } catch (error) {\n if (existsSync(execPath)) {\n return execPath;\n }\n\n throw error;\n }\n }\n\n /**\n * Check if Chrome for Testing is installed\n */\n async isInstalled(): Promise<boolean> {\n return existsSync(this.getExpectedExecutablePath());\n }\n\n /**\n * Get the installed version from the version file\n */\n async getInstalledVersion(): Promise<string | null> {\n const versionFile = join(this.cacheDir, 'version.txt');\n if (!existsSync(versionFile)) {\n return null;\n }\n try {\n const version = await readFile(versionFile, 'utf-8');\n return version.trim();\n } catch {\n return null;\n }\n }\n\n /**\n * Force re-download of Chrome for Testing\n */\n async update(): Promise<string> {\n const chromeDir = join(this.cacheDir, 'chrome');\n if (existsSync(chromeDir)) {\n await rm(chromeDir, { recursive: true, force: true });\n }\n return this.download(await this.fetchVersionInfo(this.getRequestedVersion()));\n }\n\n /**\n * Download Chrome for Testing\n */\n private async download(versionInfo: CftVersionInfo): Promise<string> {\n mkdirSync(this.cacheDir, { recursive: true });\n const downloadUrl = this.getDownloadUrl(versionInfo);\n\n if (!downloadUrl) {\n throw new Error(`No Chrome for Testing download available for ${this.getPlatformKey()}`);\n }\n\n const zipPath = join(this.cacheDir, 'chrome.zip');\n const extractDir = join(this.cacheDir, 'chrome');\n\n await this.downloadFile(downloadUrl, zipPath);\n await this.extractZip(zipPath, extractDir);\n await rm(zipPath, { force: true });\n await writeFile(join(this.cacheDir, 'version.txt'), versionInfo.version, 'utf-8');\n\n const execPath = this.getExpectedExecutablePath();\n if (!existsSync(execPath)) {\n throw new Error(`Chrome for Testing executable not found at expected path: ${execPath}`);\n }\n\n if (platform() !== 'win32') {\n await chmod(execPath, 0o755);\n }\n\n return execPath;\n }\n\n /**\n * Fetch version info from Chrome for Testing API\n */\n private async fetchVersionInfo(requestedVersion: string): Promise<CftVersionInfo> {\n const response = await fetch(ChromeForTestingService.API_URL);\n if (!response.ok) {\n throw new Error(`Failed to fetch Chrome for Testing info: ${response.statusText}`);\n }\n const data = (await response.json()) as CftApiResponse;\n const versionInfo = data.versions.find((entry) => entry.version === requestedVersion);\n if (!versionInfo) {\n throw new Error(`Chrome for Testing version ${requestedVersion} not found in feed`);\n }\n return versionInfo;\n }\n\n private getRequestedVersion(): string {\n return getRequestedChromeForTestingVersion();\n }\n\n /**\n * Get the download URL for current platform\n */\n private getDownloadUrl(versionInfo: CftVersionInfo): string | undefined {\n const platformKey = this.getPlatformKey();\n const download = versionInfo.downloads.chrome.find((d) => d.platform === platformKey);\n return download?.url;\n }\n\n /**\n * Get platform key for Chrome for Testing API\n */\n private getPlatformKey(): string {\n const os = platform();\n const architecture = arch();\n\n if (os === 'darwin') {\n return architecture === 'arm64' ? 'mac-arm64' : 'mac-x64';\n }\n if (os === 'linux') {\n return 'linux64';\n }\n if (os === 'win32') {\n return architecture === 'x64' ? 'win64' : 'win32';\n }\n throw new Error(`Unsupported platform: ${os}`);\n }\n\n /**\n * Get the expected executable path based on platform\n */\n private getExpectedExecutablePath(): string {\n const os = platform();\n const chromeDir = join(this.cacheDir, 'chrome');\n\n if (os === 'darwin') {\n const archSuffix = arch() === 'arm64' ? 'arm64' : 'x64';\n return join(\n chromeDir,\n `chrome-mac-${archSuffix}`,\n 'Google Chrome for Testing.app',\n 'Contents',\n 'MacOS',\n 'Google Chrome for Testing',\n );\n }\n if (os === 'linux') {\n return join(chromeDir, 'chrome-linux64', 'chrome');\n }\n if (os === 'win32') {\n const archSuffix = arch() === 'x64' ? '64' : '32';\n return join(chromeDir, `chrome-win${archSuffix}`, 'chrome.exe');\n }\n throw new Error(`Unsupported platform: ${os}`);\n }\n\n /**\n * Download a file from URL to local path\n */\n private async downloadFile(url: string, destPath: string): Promise<void> {\n const response = await fetch(url);\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download: ${response.statusText}`);\n }\n\n const tempPath = `${destPath}.tmp`;\n const fileStream = createWriteStream(tempPath);\n\n const reader = response.body.getReader();\n const nodeStream = new Readable({\n async read() {\n const { done, value } = await reader.read();\n if (done) {\n this.push(null);\n } else {\n this.push(Buffer.from(value));\n }\n },\n });\n\n await pipeline(nodeStream, fileStream);\n await rename(tempPath, destPath);\n }\n\n /**\n * Extract a zip file to destination directory\n */\n private async extractZip(zipPath: string, destDir: string): Promise<void> {\n const os = platform();\n\n return new Promise((resolve, reject) => {\n let proc: ChildProcess;\n\n if (os === 'win32') {\n proc = spawn('powershell', [\n '-NoProfile',\n '-Command',\n `Expand-Archive -Path \"${zipPath}\" -DestinationPath \"${destDir}\" -Force`,\n ]);\n } else {\n mkdirSync(destDir, { recursive: true });\n proc = spawn('unzip', ['-o', '-q', zipPath, '-d', destDir]);\n }\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Failed to extract zip (exit code: ${code})`));\n }\n });\n\n proc.on('error', (err) => {\n reject(new Error(`Failed to spawn extraction process: ${err.message}`));\n });\n });\n }\n}\n","import { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport { getRequestedChromeForTestingVersion } from '../services/ChromeForTestingService.js';\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\nconst DEFAULT_DOCKER_IMAGE = chromeForTestingConfig.dockerImage;\nconst DEFAULT_DOCKER_PLATFORM = chromeForTestingConfig.dockerPlatform;\nconst DEFAULT_DOCKER_REPOSITORY = DEFAULT_DOCKER_IMAGE.split(':')[0] ?? DEFAULT_DOCKER_IMAGE;\nconst PACKAGE_NAME = '@agimon-ai/browse-tool';\n\nexport interface DockerChromeForTestingBuildOptions {\n version?: string;\n image?: string;\n platform?: string;\n stdio?: 'inherit' | 'pipe' | 'ignore';\n}\n\ninterface DockerCommandResult {\n exitCode: number;\n stderr: string;\n}\n\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let current = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (pathExists(path.join(current, marker))) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n return startPath;\n }\n current = parent;\n }\n}\n\nfunction pathExists(candidatePath: string): boolean {\n return fs.existsSync(candidatePath);\n}\n\nfunction getDefaultDockerImageRepository(): string {\n return DEFAULT_DOCKER_REPOSITORY;\n}\n\nfunction getDefaultDockerImage(version: string): string {\n return `${getDefaultDockerImageRepository()}:${version}`;\n}\n\nfunction resolvePackageRoot(startDir: string): string {\n let current = path.resolve(startDir);\n\n for (let index = 0; index < 8; index += 1) {\n const packageJsonPath = path.join(current, 'package.json');\n if (pathExists(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { name?: string };\n if (packageJson.name === PACKAGE_NAME) {\n return current;\n }\n } catch {\n // Keep walking.\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n\n return path.resolve(startDir, '../..');\n}\n\nfunction resolveDockerfilePath(): string {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(resolvePackageRoot(currentDir), 'docker/chrome-for-testing.Dockerfile');\n}\n\nfunction resolveBuildContext(): string {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = resolvePackageRoot(currentDir);\n return resolveWorkspaceRoot(packageRoot);\n}\n\nfunction resolveVersionFromImage(image?: string): string | undefined {\n if (!image?.includes(':')) {\n return undefined;\n }\n\n const [, tag] = image.split(':', 2);\n const normalizedTag = tag?.trim();\n if (!normalizedTag || !/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(normalizedTag)) {\n return undefined;\n }\n\n return normalizedTag;\n}\n\nfunction normalizePlatform(platform?: string): string {\n return (platform?.trim() || DEFAULT_DOCKER_PLATFORM).toLowerCase();\n}\n\nexport function resolveChromeForTestingArchivePlatform(platform?: string): string {\n const normalized = normalizePlatform(platform);\n\n if (normalized.includes('arm64') || normalized.includes('aarch64')) {\n return 'linux-arm64';\n }\n\n return 'linux64';\n}\n\nexport function isManagedDockerChromeForTestingImage(image: string): boolean {\n return image.startsWith(`${getDefaultDockerImageRepository()}:`);\n}\n\nasync function runDockerCommand(args: string[], stdio: 'inherit' | 'pipe' | 'ignore'): Promise<DockerCommandResult> {\n return await new Promise<DockerCommandResult>((resolve, reject) => {\n let stderr = '';\n const child = spawn('docker', args, {\n stdio: stdio === 'pipe' ? ['ignore', 'pipe', 'pipe'] : ['ignore', stdio, stdio],\n });\n\n if (stdio === 'pipe') {\n child.stderr?.on('data', (chunk) => {\n stderr += chunk.toString('utf8');\n });\n }\n\n child.once('error', reject);\n child.once('close', (code) => {\n resolve({\n exitCode: code ?? 1,\n stderr,\n });\n });\n });\n}\n\nexport async function dockerImageExistsLocally(image: string): Promise<boolean> {\n const result = await runDockerCommand(['image', 'inspect', image], 'ignore');\n return result.exitCode === 0;\n}\n\nexport async function buildDockerChromeForTestingImage(\n options: DockerChromeForTestingBuildOptions = {},\n): Promise<{ image: string; version: string; platform: string }> {\n const version =\n options.version?.trim() || resolveVersionFromImage(options.image) || getRequestedChromeForTestingVersion();\n const image = options.image?.trim() || getDefaultDockerImage(version);\n const platform = normalizePlatform(options.platform);\n const archivePlatform = resolveChromeForTestingArchivePlatform(platform);\n const stdio = options.stdio ?? 'inherit';\n const args = [\n 'buildx',\n 'build',\n '--platform',\n platform,\n '--load',\n '-t',\n image,\n '--build-arg',\n `CFT_VERSION=${version}`,\n '--build-arg',\n `CFT_ARCHIVE_PLATFORM=${archivePlatform}`,\n '-f',\n resolveDockerfilePath(),\n resolveBuildContext(),\n ];\n\n const result = await runDockerCommand(args, stdio);\n if (result.exitCode !== 0) {\n const stderr = result.stderr.trim();\n throw new Error(\n stderr ? `Failed to build Docker image ${image}: ${stderr}` : `Failed to build Docker image ${image}`,\n );\n }\n\n return { image, version, platform };\n}\n\nexport async function ensureManagedDockerChromeForTestingImageAvailable(input: {\n image: string;\n platform?: string;\n}): Promise<boolean> {\n if (!isManagedDockerChromeForTestingImage(input.image)) {\n return false;\n }\n\n const exists = await dockerImageExistsLocally(input.image);\n if (exists) {\n return false;\n }\n\n await buildDockerChromeForTestingImage({\n image: input.image,\n platform: input.platform,\n stdio: 'inherit',\n });\n return true;\n}\n","// @scaffold-generated\n/**\n * Process Tree Utilities\n *\n * DESIGN PATTERNS:\n * - Pure functions for process tree operations\n * - Single responsibility per function\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Stateful logic (use services for state)\n *\n * PURPOSE:\n * On macOS, Chrome spawns multiple child helper processes (GPU, renderer, utility).\n * Killing only the parent PID leaves these children alive, causing the Chrome icon\n * to linger in the dock/tray. This utility recursively discovers and kills the\n * entire process tree.\n */\n\nimport { execSync } from 'node:child_process';\n\nfunction readChildPidMap(): Map<number, number[]> {\n const childPidMap = new Map<number, number[]>();\n const output = execSync('ps -axo pid=,ppid=', {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 3000,\n });\n\n for (const line of output.split('\\n')) {\n const [pidText, parentPidText] = line.trim().split(/\\s+/, 2);\n if (!pidText || !parentPidText) {\n continue;\n }\n\n const childPid = Number(pidText);\n const parentPid = Number(parentPidText);\n if (Number.isNaN(childPid) || Number.isNaN(parentPid)) {\n continue;\n }\n\n const siblings = childPidMap.get(parentPid);\n if (siblings) {\n siblings.push(childPid);\n } else {\n childPidMap.set(parentPid, [childPid]);\n }\n }\n\n return childPidMap;\n}\n\n/**\n * Get all descendant PIDs of a process (children, grandchildren, etc.).\n * Uses `pgrep -P` on macOS/Linux to discover child processes.\n * Returns PIDs in deepest-first order for bottom-up termination.\n */\nexport function getDescendantPids(pid: number): number[] {\n const descendants: number[] = [];\n try {\n let childPids: number[];\n try {\n const output = execSync(`pgrep -P ${pid}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n timeout: 3000,\n });\n childPids = output\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map(Number)\n .filter((childPid) => !Number.isNaN(childPid));\n } catch {\n childPids = readChildPidMap().get(pid) ?? [];\n }\n\n for (const childPid of childPids) {\n // Recurse into grandchildren first (deepest-first order)\n descendants.push(...getDescendantPids(childPid));\n descendants.push(childPid);\n }\n } catch {\n // pgrep exits with code 1 when no children found — expected\n }\n return descendants;\n}\n\n/**\n * Check if a process is still running by sending signal 0.\n */\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a process and all its descendants.\n *\n * 1. Discovers all child processes recursively\n * 2. Sends SIGTERM to children (deepest first) then parent\n * 3. Polls for up to `gracePeriodMs` for processes to exit\n * 4. Sends SIGKILL to any survivors\n */\nexport async function killProcessTree(pid: number, gracePeriodMs = 3000): Promise<void> {\n if (!isProcessRunning(pid)) {\n return;\n }\n\n // Collect all descendant PIDs (deepest-first), then parent last\n const descendants = getDescendantPids(pid);\n const allPids = [...descendants, pid];\n\n // Send SIGTERM to all processes\n for (const p of allPids) {\n try {\n process.kill(p, 'SIGTERM');\n } catch {\n // Process may have already exited\n }\n }\n\n // Poll until all processes exit or grace period expires\n const deadline = Date.now() + gracePeriodMs;\n while (Date.now() < deadline) {\n if (!allPids.some(isProcessRunning)) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n // Force kill survivors\n for (const p of allPids) {\n try {\n if (isProcessRunning(p)) {\n process.kill(p, 'SIGKILL');\n }\n } catch {\n // Process may have already exited\n }\n }\n}\n","// @scaffold-generated\n/**\n * Proxy Auth Extension Utilities\n *\n * DESIGN PATTERNS:\n * - Pure functions with no side effects (except file I/O for temp extension)\n * - Single responsibility per function\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Stateful logic (use services for state)\n *\n * PURPOSE:\n * Chrome does not natively handle proxy authentication (username/password)\n * when using --proxy-server CLI flags. This utility generates a temporary\n * Manifest V3 Chrome extension that intercepts proxy auth challenges via\n * chrome.webRequest.onAuthRequired and supplies credentials automatically.\n */\n\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/** Proxy configuration for browser launch */\nexport interface ProxyConfig {\n /** Proxy server URL, e.g. \"http://proxy.example.com:8080\" */\n server: string;\n /** Proxy username for authentication */\n username?: string;\n /** Proxy password for authentication */\n password?: string;\n /** Comma-separated list of hosts to bypass the proxy */\n bypass?: string;\n}\n\n/**\n * Generate a temporary Manifest V3 Chrome extension that handles proxy authentication.\n * Writes manifest.json + background.js to a temp directory and returns the path.\n * The extension intercepts chrome.webRequest.onAuthRequired to auto-fill credentials.\n */\nexport function createProxyAuthExtension(username: string, password: string): string {\n const extDir = path.join(os.tmpdir(), `proxy-auth-ext-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);\n fs.mkdirSync(extDir, { recursive: true });\n\n const manifest = {\n manifest_version: 3,\n name: 'Proxy Auth Helper',\n version: '1.0.0',\n permissions: ['webRequest', 'webRequestAuthProvider'],\n host_permissions: ['<all_urls>'],\n background: {\n service_worker: 'background.js',\n },\n };\n\n const safeUsername = JSON.stringify(username);\n const safePassword = JSON.stringify(password);\n\n const backgroundScript = [\n 'chrome.webRequest.onAuthRequired.addListener(',\n ` (details) => ({ authCredentials: { username: ${safeUsername}, password: ${safePassword} } }),`,\n ' { urls: [\"<all_urls>\"] },',\n ' [\"blocking\"]',\n ');',\n '',\n ].join('\\n');\n\n fs.writeFileSync(path.join(extDir, 'manifest.json'), JSON.stringify(manifest, null, 2));\n fs.writeFileSync(path.join(extDir, 'background.js'), backgroundScript);\n\n return extDir;\n}\n\n/**\n * Build Chrome CLI args for proxy configuration.\n * Returns an array of args to append to the Chrome launch command.\n */\nexport function buildProxyArgs(proxy: ProxyConfig): string[] {\n const args: string[] = [`--proxy-server=${proxy.server}`];\n\n if (proxy.bypass) {\n args.push(`--proxy-bypass-list=${proxy.bypass}`);\n }\n\n return args;\n}\n","/**\n * BrowserService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n *\n * AVOID:\n * - Mixing concerns (keep focused on browser lifecycle)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { inject, injectable, optional } from 'inversify';\nimport type { Browser, BrowserContext, BrowserType, Page } from 'playwright';\nimport { chromium, firefox, webkit } from 'playwright';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport { ensureManagedDockerChromeForTestingImageAvailable } from '../utils/dockerChromeForTesting.js';\nimport { killProcessTree } from '../utils/processTree.js';\nimport { type ProxyConfig, buildProxyArgs, createProxyAuthExtension } from '../utils/proxyAuthExtension.js';\nimport type { IBrowserLockManager } from './BrowserLockManager.js';\nimport type { IChromeForTestingService } from './ChromeForTestingService.js';\nimport { shouldCleanupRecorderPageAfterStop } from './ChromeForTestingService.js';\nimport type { IExtensionSessionRegistry } from './ExtensionSessionRegistry.js';\nimport type { ExtensionTaskResult, ExtensionTaskQueue } from './ExtensionTaskQueue.js';\nimport type { IPageMonitorService } from './PageMonitorService.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport type { BrowserProfile, IProfileService } from './ProfileService.js';\nimport type { WebSocketHub } from './WebSocketHub.js';\n\nconst DOCKER_VM_ALIASES = new Set(['docker', 'docker-vm', 'docker-chromium', 'docker-chrome-testing']);\nconst DEFAULT_VM_DOCKER_IMAGE = chromeForTestingConfig.dockerImage;\nconst DEFAULT_VM_DOCKER_BROWSER_BINARY = chromeForTestingConfig.dockerBrowserBinary;\nconst DEFAULT_VM_DOCKER_EXTENSION_PATH = '/vm/extensions/playwright';\nconst DEFAULT_VM_DOCKER_USER_DATA_DIR = '/vm/user-data';\nconst DEFAULT_VM_DOCKER_SHM_SIZE = '2g';\nconst DEFAULT_VM_DOCKER_RUNTIME_DIR = '/tmp/runtime-seluser';\nconst DEFAULT_VM_BROWSER_WINDOW_SIZE = '1920,1080';\nconst DEFAULT_VM_XVFB_SCREEN_SIZE = '1920x1080x24';\nconst EXTENSION_STARTUP_TIMEOUT_MS = 5000;\nconst EXTENSION_STARTUP_GRACE_TIMEOUT_MS = 5000;\nconst DOCKER_EXTENSION_STARTUP_TIMEOUT_MS = 12000;\nconst DOCKER_EXTENSION_STARTUP_GRACE_TIMEOUT_MS = 8000;\nconst EXTENSION_READY_STABILITY_MS = 1500;\nconst EXTENSION_STARTUP_POLL_INTERVAL_MS = 100;\nconst PROCESS_OUTPUT_CAPTURE_LIMIT = 32 * 1024;\nconst EXTENSION_LAUNCH_MAX_ATTEMPTS = 3;\nconst EXTENSION_LAUNCH_RETRY_DELAY_MS = 250;\nconst DOCKER_RETRY_CLEANUP_DELAY_MS = 250;\nconst EXTENSION_RECORDING_FINALIZE_TIMEOUT_MS = 1500;\nconst EXTENSION_RECORDING_ARTIFACT_WAIT_MS = 4000;\nconst EXTENSION_RECORDING_ARTIFACT_POLL_MS = 100;\nconst DEBUG_EXTENSION_RECORDING_STDOUT = process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1';\nconst SESSION_RESTORE_ARTIFACT_NAMES = ['Current Session', 'Current Tabs', 'Last Session', 'Last Tabs', 'Sessions'];\nconst SESSION_RESTORE_PROFILE_DIRS = new Set(['Default', 'Guest Profile', 'System Profile']);\n\n/**\n * Result of launching a browser in extension mode (no Playwright/CDP)\n * Extension mode spawns Chrome directly for bot-detection-free automation\n */\nexport interface ExtensionBrowserLaunchResult {\n /** Browser instance */\n browserInstance: BrowserInstance;\n /** Virtual page ID for tracking */\n pageId: string;\n /** Chrome process handle */\n process: ChildProcess;\n}\n\n/**\n * Execution mode for browser automation\n * - 'playwright': Uses Playwright APIs for browser control\n * - 'extension': Delegates to Chrome extension for bot-detection-free automation\n * - 'vm': Launches Chrome in an isolated runtime (for example via Docker)\n */\nexport type BrowserMode = 'playwright' | 'extension' | 'vm';\n\nexport interface VideoRecordingOptions {\n /** Directory to save video files */\n dir: string;\n /** Video dimensions (defaults to viewport size) */\n size?: { width: number; height: number };\n}\n\ninterface ExtensionRecordingSession {\n pageId: string;\n outputPath: string;\n chunkPath: string;\n active: boolean;\n chunkCount: number;\n chunkBytes: number;\n startPromise?: Promise<void>;\n}\n\nexport interface LaunchOptions {\n /** Profile name from the profile catalog */\n profileName?: string;\n /** Optional explicit browser ID */\n browserId?: string;\n /** Browser type to launch */\n browserType?: 'chromium' | 'firefox' | 'webkit';\n /** Run browser in headless mode */\n headless?: boolean;\n /** Callback when page is closed */\n onPageClose?: (pageId: string) => void;\n /** Base URL for relative navigation (e.g., page.goto('/')) */\n baseURL?: string;\n /** Video recording options. If provided, video will be recorded for all pages */\n recordVideo?: VideoRecordingOptions;\n /** Proxy configuration for routing traffic through a proxy server */\n proxy?: ProxyConfig;\n}\n\nexport interface BrowserInstance {\n /** Unique browser instance ID */\n id: string;\n /** Execution mode for this browser instance */\n mode: BrowserMode;\n /** Playwright Browser instance (undefined for extension mode) */\n browser?: Browser;\n /** Browser context (undefined for extension mode) */\n context?: BrowserContext;\n /** Profile name used */\n profileName?: string;\n /** All page IDs in this browser */\n pageIds: Set<string>;\n /** Current page ID (default for operations) */\n currentPageId: string | null;\n /** Chrome process PID (for extension mode) */\n pid?: number;\n /** Creation timestamp */\n createdAt: Date;\n /** Last activity timestamp (updated on tool execution) */\n lastAccessedAt: Date;\n /** Whether this browser was created by a spec run (uses shorter idle timeout) */\n specOrigin?: boolean;\n /** Active extension-mode recording session bound to the initial page */\n extensionRecording?: ExtensionRecordingSession;\n}\n\nexport interface BrowserLaunchResult {\n /** Browser instance */\n browserInstance: BrowserInstance;\n /** Initial page ID */\n pageId: string;\n /** Initial Playwright Page */\n page: Page;\n}\n\nexport interface LaunchWithExtensionOptions {\n /** Optional command override to launch browser process */\n command?: string;\n /** Whether vm mode should prefer Chrome for Testing */\n useChromeForTesting?: boolean;\n /** Optional command arguments when using command override */\n commandArgs?: string[];\n /** Path to the extension directory */\n extensionPath?: string;\n /** Run browser in headless mode (note: extensions require headed mode) */\n headless?: boolean;\n /** Optional URL to navigate to after launch */\n url?: string;\n /** Base URL for relative navigation */\n baseURL?: string;\n /** Profile name to use for browser settings and persistent storage */\n profileName?: string;\n /** Video recording options. If provided, video will be recorded for all pages */\n recordVideo?: VideoRecordingOptions;\n /**\n * Use Playwright's bundled Chromium instead of system Chrome.\n * Chromium supports --load-extension flag, but may be more detectable.\n * Default: false (uses system Chrome which requires pre-installed extension)\n */\n useChromium?: boolean;\n /** Optional explicit browser ID */\n browserId?: string;\n /** Optional delay before returning after launch (ms) */\n startupDelayMs?: number;\n /** Browser mode for this launch path */\n mode?: BrowserMode;\n /** Proxy configuration for routing traffic through a proxy server */\n proxy?: ProxyConfig;\n /** Extra Docker run arguments for vm Docker runtime (e.g., ['-p','5901:5900']) */\n dockerRunArgs?: string[];\n /** Extra Docker environment variables for vm Docker runtime */\n dockerEnv?: Record<string, string>;\n /** Enable built-in VNC/noVNC services for Docker vm runtime */\n dockerEnableVnc?: boolean;\n}\n\ninterface DockerLaunchConfig {\n image: string;\n browserBinary: string;\n platform?: string;\n extensionPathInContainer: string;\n userDataDirInContainer: string;\n}\n\ntype ResolvedVmRuntime =\n | {\n kind: 'host';\n command: string;\n extensionPath: string;\n userDataDir: string;\n }\n | {\n kind: 'docker';\n command: 'docker';\n extensionPath: string;\n userDataDir: string;\n docker: DockerLaunchConfig;\n };\n\nexport interface IBrowserService {\n launch(options?: LaunchOptions): Promise<BrowserLaunchResult>;\n launchWithExtension(options?: LaunchWithExtensionOptions): Promise<ExtensionBrowserLaunchResult>;\n newPage(browserId: string, onClose?: (pageId: string) => void): Promise<{ pageId: string; page: Page }>;\n getBrowser(id: string): BrowserInstance | undefined;\n /** Get browser instance by profile name */\n getBrowserByProfile(profileName: string): BrowserInstance | undefined;\n getDefaultBrowser(): BrowserInstance | undefined;\n getOrCreateDefaultBrowser(options?: LaunchOptions): Promise<BrowserInstance>;\n setCurrentPage(browserId: string, pageId: string): void;\n closeBrowser(id: string): Promise<void>;\n closeAll(): Promise<void>;\n listBrowsers(): BrowserInstance[];\n /** Update lastAccessedAt timestamp for a browser */\n touchBrowser(id: string): void;\n /** Update lastAccessedAt for a browser and optional page */\n recordBrowserActivity(browserId: string, pageId?: string): boolean;\n /** Register a virtual browser for extension mode */\n registerExtensionBrowser(): string;\n /** Register an extension browser with a specific ID (for WebSocket connections) */\n registerExtensionBrowserWithId(browserId: string): void;\n /** Start an initialized extension-mode recording when the target page becomes active for tool execution */\n ensureExtensionRecordingActive(browserId: string, pageId: string): Promise<void>;\n /** Initialize and start an extension-mode recording for a page, optionally writing to a specific path */\n startPageRecording(browserId: string, pageId: string, outputPath?: string): Promise<{ outputPath: string }>;\n /** Finalize an active extension-mode recording for a browser or specific page */\n finalizeExtensionRecording(browserId: string, pageId?: string): Promise<void>;\n /** Stop an extension-mode recording and return the persisted artifact path */\n stopPageRecording(\n browserId: string,\n pageId?: string,\n options?: { includeBase64?: boolean },\n ): Promise<{ outputPath: string; fileSizeBytes: number; videoBase64?: string }>;\n /** Persist an uploaded extension recording artifact for a browser */\n persistExtensionRecordingArtifact(browserId: string, videoBase64?: string): Promise<boolean>;\n /** Persist a streamed extension recording chunk for a browser */\n persistExtensionRecordingChunk(\n browserId: string,\n chunkBase64: string,\n ): Promise<{ chunkBytes: number; totalBytes: number; chunkCount: number } | null>;\n}\n\n@injectable()\nexport class BrowserService implements IBrowserService {\n private static readonly MAX_ACTIVE_BROWSERS = 15;\n private static readonly MAX_PAGES_PER_BROWSER = 15;\n private static chromeForTestingLaunchQueue: Promise<void> = Promise.resolve();\n private browsers: Map<string, BrowserInstance> = new Map();\n private closingExtensionRecordings: Map<string, ExtensionRecordingSession> = new Map();\n /** Maps profile names to browser IDs for smart browser reuse */\n private profileToBrowserId: Map<string, string> = new Map();\n private browserIdCounter = 0;\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.ProfileService) private profileService: IProfileService,\n @inject(PLAYWRIGHT_TYPES.PageRegistry) private pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserLockManager) private lockManager: IBrowserLockManager,\n @inject(PLAYWRIGHT_TYPES.ChromeForTestingService) private chromeForTesting: IChromeForTestingService,\n @inject(PLAYWRIGHT_TYPES.PageMonitorService) @optional() private readonly pageMonitor?: IPageMonitorService,\n @inject(PLAYWRIGHT_TYPES.WebSocketHub) @optional() private readonly webSocketHub?: WebSocketHub,\n @inject(PLAYWRIGHT_TYPES.ExtensionSessionRegistry)\n @optional()\n private readonly extensionSessionRegistry?: IExtensionSessionRegistry,\n @inject(PLAYWRIGHT_TYPES.ExtensionTaskQueue)\n @optional()\n private readonly extensionTaskQueue?: ExtensionTaskQueue,\n ) {}\n\n /**\n * Gets a browser instance by profile name.\n * Enables smart browser reuse - same profile returns same browser.\n * Validates that the browser is still connected before returning.\n */\n getBrowserByProfile(profileName: string): BrowserInstance | undefined {\n const browserId = this.profileToBrowserId.get(profileName);\n if (browserId) {\n const browser = this.browsers.get(browserId);\n // Verify browser is still valid and connected\n if (browser) {\n // Check if browser/context is still alive\n const isAlive = this.isBrowserAlive(browser);\n if (isAlive) {\n return browser;\n }\n // Browser is dead - clean up stale references\n this.cleanupStaleBrowser(browserId, profileName);\n } else {\n // Clean up stale mapping\n this.profileToBrowserId.delete(profileName);\n }\n }\n return undefined;\n }\n\n /**\n * Launches a new browser instance with optional profile.\n * If a browser with the same profile already exists, creates a new page in it instead.\n */\n async launch(options: LaunchOptions = {}): Promise<BrowserLaunchResult> {\n const {\n profileName,\n browserType = 'chromium',\n headless = true,\n onPageClose,\n baseURL,\n recordVideo,\n proxy,\n browserId: requestedBrowserId,\n } = options;\n\n // Smart browser reuse: check if browser already exists for this profile\n if (profileName) {\n const existingBrowser = this.getBrowserByProfile(profileName);\n if (existingBrowser) {\n // Reuse existing browser - create new page instead of new browser\n const { pageId, page } = await this.newPage(existingBrowser.id, onPageClose);\n return { browserInstance: existingBrowser, pageId, page };\n }\n }\n\n // Enforce browser limit before launching\n await this.enforceMaxBrowserLimit();\n\n // Use profile name as browser ID if available for easier identification\n const browserId = requestedBrowserId\n ? requestedBrowserId\n : profileName\n ? `browser-${profileName}`\n : `browser-${++this.browserIdCounter}`;\n\n if (requestedBrowserId && this.browsers.has(requestedBrowserId)) {\n throw new Error(`Browser ID \"${requestedBrowserId}\" is already in use`);\n }\n const browserTypeInstance = this.getBrowserType(browserType);\n\n // Auto-create profile if it doesn't exist\n let profile = profileName ? await this.profileService.get(profileName) : null;\n if (profileName && !profile) {\n profile = await this.profileService.create({\n name: profileName,\n browserType,\n });\n }\n\n const proxyConfig = proxy\n ? { proxy: { server: proxy.server, username: proxy.username, password: proxy.password, bypass: proxy.bypass } }\n : {};\n\n let browser: Awaited<ReturnType<typeof browserTypeInstance.launch>>;\n\n // For headed chromium, try system Chrome first, then fall back to bundled Chromium.\n // This avoids requiring a separate Playwright browser download in most dev environments.\n if (!headless && browserType === 'chromium') {\n try {\n browser = await browserTypeInstance.launch({ headless, channel: 'chrome', ...proxyConfig });\n } catch {\n browser = await browserTypeInstance.launch({ headless, ...proxyConfig });\n }\n } else {\n browser = await browserTypeInstance.launch({ headless, ...proxyConfig });\n }\n const contextOptions = this.buildContextOptions(profile, baseURL, recordVideo);\n\n if (profile && profileName) {\n const storageState = await this.profileService.loadStorageState(profileName);\n if (storageState) {\n contextOptions.storageState = storageState as NonNullable<Parameters<Browser['newContext']>[0]>['storageState'];\n }\n }\n\n const context = await browser.newContext(contextOptions);\n const page = await context.newPage();\n\n const now = new Date();\n const browserInstance: BrowserInstance = {\n id: browserId,\n mode: 'playwright',\n browser,\n context,\n profileName,\n pageIds: new Set(),\n currentPageId: null,\n createdAt: now,\n lastAccessedAt: now,\n };\n\n this.browsers.set(browserId, browserInstance);\n\n // Clean up references when browser is manually closed (e.g., user force-quits)\n browser.on('disconnected', () => {\n this.cleanupStaleBrowser(browserId, profileName);\n });\n\n // Register profile-to-browser mapping for smart reuse\n if (profileName) {\n this.profileToBrowserId.set(profileName, browserId);\n }\n\n const pageId = await this.pageRegistry.register({\n page,\n browser,\n context,\n browserId,\n profileName,\n onClose: (pid) => {\n browserInstance.pageIds.delete(pid);\n if (browserInstance.currentPageId === pid) {\n const remaining = Array.from(browserInstance.pageIds);\n browserInstance.currentPageId = remaining.length > 0 ? remaining[0] : null;\n }\n onPageClose?.(pid);\n // Auto-close browser when last page is closed to prevent orphaned processes\n if (browserInstance.pageIds.size === 0) {\n this.closeBrowser(browserId).catch(() => {});\n }\n },\n });\n\n this.pageMonitor?.startMonitoring(pageId, page);\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n\n return { browserInstance, pageId, page };\n }\n\n /**\n * Launches Chrome directly with extension - NO Playwright, NO CDP.\n * Spawns Chrome as a native process for bot-detection-free automation.\n * Only the extension can control the browser via Chrome Extension APIs.\n */\n async launchWithExtension(options: LaunchWithExtensionOptions = {}): Promise<ExtensionBrowserLaunchResult> {\n const {\n profileName,\n command,\n commandArgs = [],\n browserId: requestedBrowserId,\n mode: launchMode = 'extension',\n } = options;\n\n // Smart browser reuse: check if browser already exists for this profile\n if (profileName) {\n const existingBrowser = this.getBrowserByProfile(profileName);\n if (existingBrowser) {\n if (!this.canReuseExtensionBrowser(existingBrowser)) {\n await this.closeBrowser(existingBrowser.id).catch(() => {\n this.cleanupStaleBrowser(existingBrowser.id, profileName);\n });\n } else {\n const pageId = this.getTrackedExtensionPageId(existingBrowser);\n this.recordBrowserActivity(existingBrowser.id, pageId);\n const dummyProcess = { pid: existingBrowser.pid } as ChildProcess;\n return { browserInstance: existingBrowser, pageId, process: dummyProcess };\n }\n }\n }\n\n await this.enforceMaxBrowserLimit();\n\n const extensionHostPath = this.resolveExtensionPath(options.extensionPath);\n const manifestPath = path.join(extensionHostPath, 'manifest.json');\n if (!fs.existsSync(manifestPath)) {\n throw new Error(\n `Extension manifest not found at ${manifestPath}. Please build the extension first: pnpm build:extension`,\n );\n }\n\n const browserId = requestedBrowserId\n ? requestedBrowserId\n : profileName\n ? `browser-${profileName}`\n : `browser-${++this.browserIdCounter}`;\n\n if (requestedBrowserId && this.browsers.has(requestedBrowserId)) {\n throw new Error(`Browser ID \"${requestedBrowserId}\" is already in use`);\n }\n\n if (profileName) {\n const existingProfile = await this.profileService.get(profileName);\n if (!existingProfile) {\n await this.profileService.create({ name: profileName, browserType: 'chromium' });\n }\n }\n\n const userDataDir = this.createUserDataDir(profileName);\n if (userDataDir) {\n await this.lockManager.ensureAvailable(userDataDir);\n }\n\n const vmRuntime = await this.resolveVmRuntime({\n command,\n launchMode,\n useChromeForTesting: options.useChromeForTesting ?? launchMode === 'vm',\n extensionHostPath,\n requestedRuntimeExtensionPath: options.extensionPath,\n userDataDir,\n });\n\n if (vmRuntime.kind === 'docker') {\n try {\n this.ensureDockerWritableUserDataDir(userDataDir);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to prepare Docker user-data directory \"${userDataDir}\": ${message}`, {\n cause: error,\n });\n }\n }\n\n // If proxy auth is needed, generate a temp extension to handle credentials\n const extensionPaths = [vmRuntime.extensionPath];\n const dockerVolumeArgs: string[] = [];\n\n if (vmRuntime.kind === 'docker') {\n dockerVolumeArgs.push(...this.createDockerVolumeArgs(extensionHostPath, vmRuntime.extensionPath, true));\n }\n\n if (options.proxy?.username && options.proxy?.password) {\n const proxyAuthPath = createProxyAuthExtension(options.proxy.username, options.proxy.password);\n\n if (vmRuntime.kind === 'docker') {\n const proxyPathInContainer = `${vmRuntime.docker.extensionPathInContainer}-proxy-auth-${Date.now()}`;\n extensionPaths.push(proxyPathInContainer);\n dockerVolumeArgs.push(...this.createDockerVolumeArgs(proxyAuthPath, proxyPathInContainer, true));\n } else {\n extensionPaths.push(proxyAuthPath);\n }\n }\n\n const loadExtensions = extensionPaths.join(',');\n const autoSelectTabCaptureSourceTitle = process.env.BROWSE_TOOL_AUTO_SELECT_TAB_CAPTURE_SOURCE_TITLE?.trim();\n const autoSelectDesktopCaptureSource = process.env.BROWSE_TOOL_AUTO_SELECT_DESKTOP_CAPTURE_SOURCE?.trim();\n const useFakeUiForMediaStream = process.env.BROWSE_TOOL_USE_FAKE_UI_FOR_MEDIA_STREAM === '1';\n\n // Chrome args for stealth mode - NO CDP, NO automation flags\n const defaultArgs: string[] = [\n `--user-data-dir=${vmRuntime.userDataDir}`,\n `--load-extension=${loadExtensions}`,\n `--disable-extensions-except=${loadExtensions}`,\n '--enable-extensions',\n '--auto-accept-this-tab-capture',\n ...(useFakeUiForMediaStream ? ['--use-fake-ui-for-media-stream'] : []),\n ...(autoSelectTabCaptureSourceTitle\n ? [`--auto-select-tab-capture-source-by-title=${autoSelectTabCaptureSourceTitle}`]\n : []),\n ...(autoSelectDesktopCaptureSource\n ? [`--auto-select-desktop-capture-source=${autoSelectDesktopCaptureSource}`]\n : []),\n '--disable-blink-features=AutomationControlled',\n '--disable-infobars',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n '--disable-component-update',\n '--disable-features=TranslateUI',\n '--disable-ipc-flooding-protection',\n '--password-store=basic',\n '--use-mock-keychain',\n '--disable-dev-shm-usage',\n ...(vmRuntime.kind === 'docker' ? ['--no-sandbox'] : []),\n `--window-size=${DEFAULT_VM_BROWSER_WINDOW_SIZE}`,\n ...(options.proxy ? buildProxyArgs(options.proxy) : []),\n options.url ?? 'about:blank',\n ];\n\n const isUrlLike = (value: string): boolean => {\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'file:';\n } catch {\n return false;\n }\n };\n\n const normalizeUrlLikeArg = (value: string): string => {\n if (!isUrlLike(value)) {\n return value;\n }\n try {\n const parsed = new URL(value);\n const normalizedPath =\n parsed.pathname.endsWith('/') && parsed.pathname !== '/' ? parsed.pathname.slice(0, -1) : parsed.pathname;\n return `${parsed.protocol}//${parsed.host}${normalizedPath}${parsed.search}`;\n } catch {\n return value;\n }\n };\n\n const launchUrl = options.url ?? 'about:blank';\n const normalizedLaunchUrl = normalizeUrlLikeArg(launchUrl);\n\n const args =\n launchMode === 'vm' && commandArgs.length > 0\n ? [\n ...defaultArgs,\n ...commandArgs.filter((arg) => {\n if (!isUrlLike(arg)) {\n return true;\n }\n return normalizeUrlLikeArg(arg) !== normalizedLaunchUrl;\n }),\n ]\n : commandArgs.length > 0\n ? commandArgs\n : defaultArgs;\n\n const spawnArgs =\n vmRuntime.kind === 'docker'\n ? [\n ...this.buildDockerSpawnArgs(\n vmRuntime.docker,\n userDataDir,\n dockerVolumeArgs,\n options.dockerRunArgs,\n options.dockerEnv,\n options.dockerEnableVnc,\n ),\n ...args,\n ]\n : args;\n\n let lastError: Error | undefined;\n let dockerRetryCleanupAttempts = 0;\n let lastDockerRetryCleanupStatus: string | undefined;\n\n for (let attempt = 1; attempt <= EXTENSION_LAUNCH_MAX_ATTEMPTS; attempt += 1) {\n try {\n const launchOperation = () =>\n this.startExtensionBrowser({\n browserId,\n launchMode,\n profileName,\n vmRuntime,\n spawnArgs,\n recordVideo: options.recordVideo,\n startupDelayMs: options.startupDelayMs,\n launchUrl: options.url,\n });\n const result = this.shouldSerializeChromeForTestingLaunch(vmRuntime)\n ? await this.runSerializedChromeForTestingLaunch(launchOperation)\n : await launchOperation();\n return result;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n const retryable = this.isRetryableExtensionLaunchError(lastError);\n if (!retryable || attempt === EXTENSION_LAUNCH_MAX_ATTEMPTS) {\n if (vmRuntime.kind === 'docker' && (dockerRetryCleanupAttempts > 0 || lastDockerRetryCleanupStatus)) {\n const retryDiagnostics = [\n dockerRetryCleanupAttempts > 0 ? `Docker retry cleanup attempts: ${dockerRetryCleanupAttempts}.` : '',\n lastDockerRetryCleanupStatus ? `Last Docker retry cleanup: ${lastDockerRetryCleanupStatus}.` : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n lastError.message = `${lastError.message} ${retryDiagnostics}`.trim();\n }\n\n throw lastError;\n }\n\n if (vmRuntime.kind === 'docker') {\n dockerRetryCleanupAttempts += 1;\n lastDockerRetryCleanupStatus = await this.prepareDockerRetryUserDataDir(userDataDir);\n }\n\n await this.delay(EXTENSION_LAUNCH_RETRY_DELAY_MS * attempt);\n }\n }\n\n throw lastError ?? new Error('Extension browser launch failed');\n }\n\n /**\n * Resolve launch runtime for extension-style launches.\n * In vm mode this can resolve either a local command runtime or Docker runtime.\n */\n private async resolveVmRuntime(input: {\n command?: string;\n launchMode: BrowserMode;\n useChromeForTesting?: boolean;\n extensionHostPath: string;\n requestedRuntimeExtensionPath?: string;\n userDataDir: string;\n }): Promise<ResolvedVmRuntime> {\n const { command, launchMode, useChromeForTesting, extensionHostPath, requestedRuntimeExtensionPath, userDataDir } =\n input;\n\n if (launchMode === 'vm' && this.isDockerVmAlias(command)) {\n const extensionPathInContainer = this.resolveDockerRuntimeExtensionPath(requestedRuntimeExtensionPath);\n const userDataDirInContainer = process.env.PLAYWRIGHT_VM_DOCKER_USER_DATA_DIR || DEFAULT_VM_DOCKER_USER_DATA_DIR;\n const image = process.env.PLAYWRIGHT_VM_DOCKER_IMAGE || DEFAULT_VM_DOCKER_IMAGE;\n const platform = process.env.PLAYWRIGHT_VM_DOCKER_PLATFORM || chromeForTestingConfig.dockerPlatform;\n\n await ensureManagedDockerChromeForTestingImageAvailable({\n image,\n platform,\n });\n\n return {\n kind: 'docker',\n command: 'docker',\n extensionPath: extensionPathInContainer,\n userDataDir: userDataDirInContainer,\n docker: {\n image,\n browserBinary: process.env.PLAYWRIGHT_VM_DOCKER_BROWSER_BINARY || DEFAULT_VM_DOCKER_BROWSER_BINARY,\n platform,\n extensionPathInContainer,\n userDataDirInContainer,\n },\n };\n }\n\n const launchCommand = await this.resolveLaunchCommand({\n command,\n launchMode,\n useChromeForTesting,\n });\n\n return {\n kind: 'host',\n command: launchCommand,\n extensionPath: extensionHostPath,\n userDataDir,\n };\n }\n\n /**\n * Resolve extension path inside Docker runtime.\n * If provided path looks like a host extension directory (contains manifest),\n * keep runtime path at default/env and only use it for host mounting.\n * Otherwise treat the value as an explicit container runtime path.\n */\n private resolveDockerRuntimeExtensionPath(requestedRuntimeExtensionPath?: string): string {\n if (requestedRuntimeExtensionPath && !fs.existsSync(path.join(requestedRuntimeExtensionPath, 'manifest.json'))) {\n return requestedRuntimeExtensionPath;\n }\n return process.env.PLAYWRIGHT_VM_DOCKER_EXTENSION_PATH || DEFAULT_VM_DOCKER_EXTENSION_PATH;\n }\n\n /**\n * Create docker volume arguments for `docker run`.\n */\n private createDockerVolumeArgs(hostPath: string, containerPath: string, readOnly: boolean): string[] {\n const normalizedHostPath = path.resolve(hostPath);\n const volume = `${normalizedHostPath}:${containerPath}${readOnly ? ':ro' : ''}`;\n return ['-v', volume];\n }\n\n /**\n * Ensure user-data mounts are writable by non-host users inside Docker.\n * This avoids immediate browser exit when container UID differs from host UID.\n */\n private ensureDockerWritableUserDataDir(userDataDir: string): void {\n const rootDirMode = 0o777;\n const fileMode = 0o666;\n\n fs.mkdirSync(userDataDir, { recursive: true, mode: rootDirMode });\n\n const stack: string[] = [userDataDir];\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) {\n continue;\n }\n\n fs.chmodSync(currentDir, rootDirMode);\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n fs.chmodSync(fullPath, entry.isDirectory() ? rootDirMode : fileMode);\n\n if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n }\n\n /**\n * Build docker invocation arguments for browser launch.\n */\n private buildDockerSpawnArgs(\n dockerConfig: DockerLaunchConfig,\n hostUserDataDir: string,\n additionalVolumeArgs: string[],\n additionalRunArgs?: string[],\n dockerEnv?: Record<string, string>,\n enableVnc?: boolean,\n ): string[] {\n const args: string[] = [\n 'run',\n '--rm',\n '--init',\n '--shm-size',\n process.env.PLAYWRIGHT_VM_DOCKER_SHM_SIZE || DEFAULT_VM_DOCKER_SHM_SIZE,\n ];\n\n if (dockerConfig.platform) {\n args.push('--platform', dockerConfig.platform);\n }\n\n if (process.env.PLAYWRIGHT_VM_DOCKER_NETWORK) {\n args.push('--network', process.env.PLAYWRIGHT_VM_DOCKER_NETWORK);\n }\n\n if (process.platform === 'linux') {\n args.push('--add-host', 'host.docker.internal:host-gateway');\n }\n\n args.push(...additionalVolumeArgs);\n args.push(...this.createDockerVolumeArgs(hostUserDataDir, dockerConfig.userDataDirInContainer, false));\n args.push(...this.createDockerEnvArgs(dockerEnv));\n args.push(...this.parseDockerRunArgs(process.env.PLAYWRIGHT_VM_DOCKER_RUN_ARGS));\n args.push(...(additionalRunArgs ?? []));\n\n if (enableVnc) {\n args.push(\n '--entrypoint',\n '/bin/bash',\n dockerConfig.image,\n '-lc',\n this.buildDockerBrowserLaunchScript(dockerConfig.browserBinary, { enableVnc: true }),\n '_',\n );\n return args;\n }\n\n args.push(\n '--entrypoint',\n '/bin/bash',\n dockerConfig.image,\n '-lc',\n this.buildDockerBrowserLaunchScript(dockerConfig.browserBinary, { enableVnc: false }),\n '_',\n );\n\n return args;\n }\n\n /**\n * Build shell script for Docker runtime that starts the services required for headed browser automation.\n */\n private buildDockerBrowserLaunchScript(\n browserBinary: string,\n options: {\n enableVnc: boolean;\n },\n ): string {\n const quotedBrowserBinary = JSON.stringify(browserBinary);\n const lines = [\n 'set -euo pipefail',\n `export XDG_RUNTIME_DIR=\"${DEFAULT_VM_DOCKER_RUNTIME_DIR}\"`,\n 'mkdir -p \"$XDG_RUNTIME_DIR\" \"$XDG_RUNTIME_DIR/pulse\"',\n 'chmod 700 \"$XDG_RUNTIME_DIR\" || true',\n 'export DBUS_SESSION_BUS_ADDRESS=\"$(dbus-daemon --session --fork --print-address --nopidfile)\"',\n 'pulseaudio --check >/dev/null 2>&1 || pulseaudio --daemonize=yes --exit-idle-time=-1 --disable-shm=true --log-target=stderr >/tmp/pulseaudio.log 2>&1',\n 'export PULSE_SERVER=\"unix:$XDG_RUNTIME_DIR/pulse/native\"',\n `Xvfb :99 -screen 0 ${DEFAULT_VM_XVFB_SCREEN_SIZE} -nolisten tcp >/tmp/xvfb.log 2>&1 &`,\n 'for i in $(seq 1 30); do xdpyinfo -display :99 >/dev/null 2>&1 && break; sleep 0.2; done',\n 'export DISPLAY=:99',\n `BROWSER_BIN=${quotedBrowserBinary}`,\n 'exec \"$BROWSER_BIN\" \"$@\"',\n ];\n\n if (options.enableVnc) {\n lines.splice(\n lines.length - 2,\n 0,\n 'if [ -n \"${SE_VNC_PASSWORD:-}\" ]; then',\n ' x11vnc -storepasswd \"${SE_VNC_PASSWORD}\" /tmp/vnc-passwd >/dev/null 2>&1',\n ' X11VNC_AUTH=\"-rfbauth /tmp/vnc-passwd\"',\n 'else',\n ' X11VNC_AUTH=\"\"',\n 'fi',\n 'x11vnc -forever -shared -rfbport \"${SE_VNC_PORT:-5900}\" -display :99 $X11VNC_AUTH >/tmp/x11vnc.log 2>&1 &',\n 'if command -v websockify >/dev/null 2>&1; then',\n ' websockify --web /opt/bin/noVNC \"${SE_NO_VNC_PORT:-7900}\" \"localhost:${SE_VNC_PORT:-5900}\" >/tmp/novnc.log 2>&1 &',\n 'fi',\n );\n }\n\n return lines.join('\\n');\n }\n\n /**\n * Convert environment map to Docker `-e KEY=VALUE` arguments.\n */\n private createDockerEnvArgs(dockerEnv?: Record<string, string>): string[] {\n if (!dockerEnv) {\n return [];\n }\n\n const args: string[] = [];\n for (const [key, value] of Object.entries(dockerEnv)) {\n if (!key) {\n continue;\n }\n args.push('-e', `${key}=${value}`);\n }\n return args;\n }\n\n /**\n * Parse additional docker run args from environment variable.\n */\n private parseDockerRunArgs(rawArgs?: string): string[] {\n if (!rawArgs) {\n return [];\n }\n return rawArgs\n .split(/\\s+/)\n .map((token) => token.trim())\n .filter((token) => token.length > 0);\n }\n\n /**\n * Check whether the provided vm command should route to Docker runtime.\n */\n private isDockerVmAlias(command?: string): boolean {\n if (!command) {\n return false;\n }\n\n const trimmed = command.trim().toLowerCase();\n return DOCKER_VM_ALIASES.has(trimmed);\n }\n\n /**\n * Resolve the browser command for extension/vm launches.\n */\n private async resolveLaunchCommand(input: {\n command?: string;\n launchMode: BrowserMode;\n useChromeForTesting?: boolean;\n }): Promise<string> {\n const { command, launchMode, useChromeForTesting } = input;\n\n if (launchMode === 'vm' && useChromeForTesting) {\n if (!command) {\n return this.findChromeExecutable();\n }\n\n if (this.isChromeTestingAlias(command) || this.isGenericChromeAlias(command)) {\n return this.chromeForTesting.getExecutablePath();\n }\n }\n\n return command || this.findChromeExecutable();\n }\n\n /**\n * Check whether a command is a Chrome Testing alias.\n */\n private isChromeTestingAlias(command: string): boolean {\n const trimmed = command.trim().toLowerCase();\n return (\n trimmed === 'chrome-testing' ||\n trimmed === 'chrome-for-testing' ||\n trimmed === 'google-chrome-for-testing' ||\n trimmed.endsWith('google chrome for testing') ||\n trimmed.endsWith('google-chrome-for-testing') ||\n trimmed.endsWith('chrome-for-testing.exe')\n );\n }\n\n /**\n * Check whether a command looks like a generic Chrome alias.\n */\n private isGenericChromeAlias(command: string): boolean {\n const trimmed = command.trim().toLowerCase();\n const binaryName = path.basename(trimmed);\n\n return binaryName === 'chrome' || binaryName === 'google-chrome' || binaryName === 'googlechrome';\n }\n\n /**\n * Resolve extension path with fallback strategies.\n * Prefers local dist/extension directory, falls back to dependency resolution.\n */\n private resolveExtensionPath(providedPath?: string): string {\n // Strategy 1: Use explicitly provided path\n if (providedPath && fs.existsSync(path.join(providedPath, 'manifest.json'))) {\n return providedPath;\n }\n\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = this.findPackageRoot(currentDir);\n\n const localCandidatePaths = [\n packageRoot ? path.resolve(packageRoot, 'dist/extension') : '',\n path.resolve(currentDir, '../dist/extension'),\n path.resolve(currentDir, '../../dist/extension'),\n ].filter(Boolean);\n\n for (const localExtPath of localCandidatePaths) {\n if (fs.existsSync(path.join(localExtPath, 'manifest.json'))) {\n return localExtPath;\n }\n }\n\n // Strategy 3: Try cwd + dist/extension (for CLI execution)\n const cwdExtPath = path.resolve(process.cwd(), 'dist/extension');\n if (fs.existsSync(path.join(cwdExtPath, 'manifest.json'))) {\n return cwdExtPath;\n }\n\n throw new Error('Chrome extension not found. Build the extension first: pnpm build:extension');\n }\n\n /**\n * Resolve the package root for this module by walking up directories\n * and matching package.json name.\n */\n private findPackageRoot(startDir: string, packageName = '@agimon-ai/browse-tool'): string | null {\n let currentDir = startDir;\n\n for (let i = 0; i < 8; i++) {\n const packageJsonPath = path.join(currentDir, 'package.json');\n\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { name?: string };\n if (packageJson?.name === packageName) {\n return currentDir;\n }\n } catch {\n // ignore invalid JSON and continue searching\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n break;\n }\n currentDir = parentDir;\n }\n\n return null;\n }\n\n /**\n * Create user data directory for Chrome\n */\n private getExtensionUserDataDir(profileName: string): string {\n return path.join(this.profileService.getProfilesDir(), profileName, 'extension-user-data');\n }\n\n private createUserDataDir(profileName?: string): string {\n if (profileName) {\n const profileDir = this.getExtensionUserDataDir(profileName);\n fs.mkdirSync(profileDir, { recursive: true });\n this.cleanupExtensionSessionRestoreArtifacts(profileDir);\n return profileDir;\n }\n return fs.mkdtempSync(path.join(os.tmpdir(), 'extension-chrome-'));\n }\n\n private cleanupExtensionSessionRestoreArtifacts(userDataDir: string): void {\n for (const profileDir of this.getSessionRestoreProfileDirs(userDataDir)) {\n for (const artifactName of SESSION_RESTORE_ARTIFACT_NAMES) {\n try {\n fs.rmSync(path.join(profileDir, artifactName), { recursive: true, force: true });\n } catch {\n // Ignore best-effort cleanup failures to avoid blocking browser launch.\n }\n }\n }\n }\n\n private getSessionRestoreProfileDirs(userDataDir: string): string[] {\n const profileDirs = [userDataDir];\n\n const directoryEntries = (() => {\n try {\n return fs.readdirSync(userDataDir, { withFileTypes: true });\n } catch {\n return null;\n }\n })();\n if (!directoryEntries) {\n return profileDirs;\n }\n\n for (const entry of directoryEntries) {\n if (!entry.isDirectory()) {\n continue;\n }\n if (SESSION_RESTORE_PROFILE_DIRS.has(entry.name) || entry.name.startsWith('Profile ')) {\n profileDirs.push(path.join(userDataDir, entry.name));\n }\n }\n\n return profileDirs;\n }\n\n /**\n * Find Chrome executable path.\n * Uses Chrome for Testing first (supports --load-extension), falls back to system Chrome.\n */\n private async findChromeExecutable(): Promise<string> {\n // Chrome for Testing supports --load-extension unlike regular Chrome\n try {\n return await this.chromeForTesting.getExecutablePath();\n } catch {\n // Fall through to system Chrome paths\n }\n\n const currentPlatform = process.platform;\n const CHROME_PATHS: Record<string, string[]> = {\n darwin: [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',\n `${os.homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,\n ],\n linux: [\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium',\n '/usr/bin/chromium-browser',\n '/snap/bin/chromium',\n ],\n win32: [\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n `${process.env.PROGRAMFILES}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n `${process.env['PROGRAMFILES(X86)']}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n ],\n };\n for (const chromePath of CHROME_PATHS[currentPlatform] ?? []) {\n if (fs.existsSync(chromePath)) return chromePath;\n }\n throw new Error('Chrome not found. Install Google Chrome or run with Chrome for Testing.');\n }\n\n /**\n * Creates a new page in an existing browser instance.\n * Only available for playwright mode browsers.\n */\n async newPage(browserId: string, onClose?: (pageId: string) => void): Promise<{ pageId: string; page: Page }> {\n const browserInstance = this.browsers.get(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n throw new Error('Cannot create new page in extension mode browser. Use extension APIs directly.');\n }\n\n if (!browserInstance.context || !browserInstance.browser) {\n throw new Error(`Browser \"${browserId}\" has no Playwright context`);\n }\n\n await this.enforceMaxPageLimit(browserInstance);\n\n const page = await browserInstance.context.newPage();\n const pageId = await this.pageRegistry.register({\n page,\n browser: browserInstance.browser,\n context: browserInstance.context,\n browserId,\n profileName: browserInstance.profileName,\n onClose: (pid) => {\n browserInstance.pageIds.delete(pid);\n if (browserInstance.currentPageId === pid) {\n const remaining = Array.from(browserInstance.pageIds);\n browserInstance.currentPageId = remaining.length > 0 ? remaining[0] : null;\n }\n onClose?.(pid);\n // Auto-close browser when last page is closed to prevent orphaned processes\n if (browserInstance.pageIds.size === 0) {\n this.closeBrowser(browserId).catch(() => {});\n }\n },\n });\n\n this.pageMonitor?.startMonitoring(pageId, page);\n browserInstance.pageIds.add(pageId);\n\n return { pageId, page };\n }\n\n /**\n * Gets a browser instance by ID.\n */\n getBrowser(id: string): BrowserInstance | undefined {\n return this.browsers.get(id);\n }\n\n /**\n * Gets the default (first available) browser instance.\n * Skips dead browsers and cleans up stale references.\n */\n getDefaultBrowser(): BrowserInstance | undefined {\n const browsers = this.listBrowsers();\n for (const browser of browsers) {\n if (this.isBrowserAlive(browser)) {\n return browser;\n }\n // Clean up dead browser\n this.cleanupStaleBrowser(browser.id, browser.profileName);\n }\n return undefined;\n }\n\n /**\n * Gets the default browser or creates one if none exists.\n */\n async getOrCreateDefaultBrowser(options?: LaunchOptions): Promise<BrowserInstance> {\n const existing = this.getDefaultBrowser();\n if (existing) {\n return existing;\n }\n const result = await this.launch(options);\n return result.browserInstance;\n }\n\n /**\n * Sets the current page for a browser instance.\n */\n setCurrentPage(browserId: string, pageId: string): void {\n const browserInstance = this.browsers.get(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n if (!browserInstance.pageIds.has(pageId)) {\n throw new Error(`Page \"${pageId}\" does not belong to browser \"${browserId}\"`);\n }\n if (browserInstance.extensionRecording?.active && browserInstance.extensionRecording.pageId !== pageId) {\n console.warn(\n `[BrowserService] Extension recording for browser \"${browserId}\" remains pinned to initial page \"${browserInstance.extensionRecording.pageId}\"`,\n );\n }\n browserInstance.currentPageId = pageId;\n }\n\n /**\n * Closes a specific browser instance.\n */\n async closeBrowser(id: string): Promise<void> {\n const browserInstance = this.browsers.get(id);\n if (browserInstance) {\n let recordingError: Error | undefined;\n const recording = browserInstance.extensionRecording;\n const recordingOutputPath = recording?.outputPath;\n const hadActiveRecording = recording?.active === true;\n let finalizeAfterClose = false;\n\n if (recording) {\n this.closingExtensionRecordings.set(id, recording);\n }\n\n if (hadActiveRecording) {\n try {\n const finalizationResult = await this.attemptExtensionRecordingFinalization(id);\n recordingError = finalizationResult.error;\n\n if (recordingError && recording && this.shouldCloseRecordedExtensionPageFirst(browserInstance)) {\n await this.closeRecordedExtensionPage(browserInstance, recording);\n if (recordingOutputPath) {\n const artifactArrived = await this.waitForExtensionRecordingArtifact(recordingOutputPath);\n if (!artifactArrived) {\n throw new Error(\n `Extension recording for browser \"${id}\" was not persisted after the recorded tab closed`,\n );\n }\n recording.active = false;\n recordingError = undefined;\n }\n }\n } catch (error) {\n recordingError = error instanceof Error ? error : new Error(String(error));\n }\n } else {\n try {\n await this.finalizeExtensionRecording(id);\n } catch (error) {\n recordingError = error instanceof Error ? error : new Error(String(error));\n }\n }\n\n // Extension mode uses context, regular mode uses browser\n if (browserInstance.browser) {\n await browserInstance.browser.close();\n } else if (browserInstance.context) {\n // For extension-style flows, context may still exist in some paths.\n if (browserInstance.pid) {\n try {\n await killProcessTree(browserInstance.pid);\n } catch {\n // Fallback to context close if tree kill fails\n try {\n const pages = browserInstance.context.pages();\n await Promise.all(\n pages.map((p) =>\n p.close().catch((pageError) => {\n // Ignore page close errors during browser shutdown\n void pageError;\n }),\n ),\n );\n await browserInstance.context.close();\n } catch (closeError) {\n // Ignore context close errors during browser shutdown\n void closeError;\n }\n }\n } else {\n // Fallback to context.close() if no PID available\n const pages = browserInstance.context.pages();\n await Promise.all(\n pages.map((p) =>\n p.close().catch((error) => {\n // Ignore page close errors during browser shutdown\n void error;\n }),\n ),\n );\n await browserInstance.context.close();\n }\n } else if (browserInstance.pid) {\n try {\n await killProcessTree(browserInstance.pid);\n } catch {\n // Ignore process cleanup failures for non-playwright browsers\n }\n }\n\n if (recordingOutputPath && recording && !fs.existsSync(recordingOutputPath)) {\n const streamedArtifactState = await this.waitForExtensionRecordingArtifactOrChunks(recording);\n let artifactArrived = streamedArtifactState.artifactReady;\n\n if (!artifactArrived && streamedArtifactState.chunkReady) {\n const finalizedFromChunks = await this.persistExtensionRecordingArtifact(id);\n artifactArrived = finalizedFromChunks || fs.existsSync(recordingOutputPath);\n }\n\n if (artifactArrived) {\n recordingError = undefined;\n } else if (recording) {\n finalizeAfterClose = true;\n }\n }\n\n // Clean up profile-to-browser mapping\n if (browserInstance.profileName) {\n this.profileToBrowserId.delete(browserInstance.profileName);\n }\n this.browsers.delete(id);\n\n if (finalizeAfterClose) {\n void this.finalizeClosingExtensionRecording(id);\n } else {\n this.closingExtensionRecordings.delete(id);\n }\n\n if (recordingError) {\n throw recordingError;\n }\n }\n }\n\n /**\n * Closes all browser instances.\n */\n async closeAll(): Promise<void> {\n const ids = Array.from(this.browsers.keys());\n await Promise.all(\n ids.map((id) =>\n this.closeBrowser(id).catch(() => {\n // Isolate failures — one browser must not block others from closing\n }),\n ),\n );\n this.browsers.clear();\n this.profileToBrowserId.clear();\n this.pageRegistry.clear();\n }\n\n /**\n * Lists all browser instances.\n */\n listBrowsers(): BrowserInstance[] {\n return Array.from(this.browsers.values());\n }\n\n /**\n * Updates lastAccessedAt timestamp for a browser to track idle state.\n */\n touchBrowser(id: string): void {\n const browserInstance = this.browsers.get(id);\n if (browserInstance) {\n browserInstance.lastAccessedAt = new Date();\n }\n }\n\n recordBrowserActivity(browserId: string, pageId?: string): boolean {\n const browserInstance = this.browsers.get(browserId);\n if (!browserInstance) {\n return false;\n }\n\n browserInstance.lastAccessedAt = new Date();\n\n if (pageId) {\n this.pageRegistry.touchPage(pageId);\n }\n\n return true;\n }\n\n /**\n * Registers a virtual browser for extension mode.\n * Creates a browser entry without Playwright instances.\n */\n registerExtensionBrowser(): string {\n const browserId = `browser-${++this.browserIdCounter}`;\n const now = new Date();\n\n const browserInstance: BrowserInstance = {\n id: browserId,\n mode: 'extension',\n browser: undefined,\n context: undefined,\n pageIds: new Set(),\n currentPageId: null,\n createdAt: now,\n lastAccessedAt: now,\n };\n\n this.browsers.set(browserId, browserInstance);\n return browserId;\n }\n\n /**\n * Registers an extension browser with a specific ID.\n * Used when an external Chrome connects via WebSocket.\n */\n registerExtensionBrowserWithId(browserId: string): void {\n if (this.browsers.has(browserId)) {\n return;\n }\n\n const now = new Date();\n const browserInstance: BrowserInstance = {\n id: browserId,\n mode: 'extension',\n browser: undefined,\n context: undefined,\n pageIds: new Set(),\n currentPageId: null,\n createdAt: now,\n lastAccessedAt: now,\n };\n\n this.browsers.set(browserId, browserInstance);\n }\n\n async ensureExtensionRecordingActive(browserId: string, pageId: string): Promise<void> {\n const browserInstance = this.browsers.get(browserId);\n const recording = browserInstance?.extensionRecording;\n\n if (!browserInstance || !recording || recording.pageId !== pageId || recording.active) {\n return;\n }\n\n if (!recording.startPromise) {\n recording.startPromise = this.startExtensionRecording(browserInstance, recording).finally(() => {\n if (browserInstance.extensionRecording === recording) {\n browserInstance.extensionRecording.startPromise = undefined;\n }\n });\n }\n\n await recording.startPromise;\n }\n\n async startPageRecording(browserId: string, pageId: string, outputPath?: string): Promise<{ outputPath: string }> {\n const browserInstance = this.browsers.get(browserId);\n if (!browserInstance) {\n throw new Error(`Browser \"${browserId}\" not found`);\n }\n\n if (browserInstance.mode !== 'extension' && browserInstance.mode !== 'vm') {\n throw new Error(`Browser \"${browserId}\" does not support extension recording in \"${browserInstance.mode}\" mode`);\n }\n\n let recording = browserInstance.extensionRecording;\n if (recording && recording.pageId !== pageId) {\n if (recording.active) {\n throw new Error(\n `Browser \"${browserId}\" is already recording page \"${recording.pageId}\". Stop the current recording before starting another one.`,\n );\n }\n\n browserInstance.extensionRecording = undefined;\n recording = undefined;\n }\n\n const resolvedOutputPath = outputPath\n ? path.resolve(outputPath)\n : (recording?.outputPath ??\n path.join(\n os.tmpdir(),\n 'browse-tool-recordings',\n `recording-${this.toSafeRecordingId(browserId)}-${Date.now()}.webm`,\n ));\n\n if (!recording) {\n this.initializeExtensionRecordingAtPath(browserInstance, pageId, resolvedOutputPath);\n recording = browserInstance.extensionRecording;\n } else if (recording.outputPath !== resolvedOutputPath) {\n if (recording.active) {\n throw new Error(\n `Browser \"${browserId}\" is already recording to \"${recording.outputPath}\". Stop the current recording before changing output path.`,\n );\n }\n\n recording.outputPath = resolvedOutputPath;\n recording.chunkPath = `${resolvedOutputPath}.part`;\n this.clearExtensionRecordingChunkState(recording);\n }\n\n if (!recording) {\n throw new Error(`Failed to initialize recording for browser \"${browserId}\"`);\n }\n\n await this.ensureExtensionRecordingActive(browserId, pageId);\n return { outputPath: recording.outputPath };\n }\n\n async finalizeExtensionRecording(browserId: string, pageId?: string): Promise<void> {\n const browserInstance = this.browsers.get(browserId);\n const recording = browserInstance?.extensionRecording;\n\n if (!browserInstance || !recording) {\n return;\n }\n\n if (pageId && recording.pageId !== pageId) {\n return;\n }\n\n if (!recording.active) {\n return;\n }\n\n if (!this.extensionTaskQueue) {\n throw new Error(`Extension recording task queue is unavailable for browser \"${browserId}\"`);\n }\n\n const cleanupRecorderPage = shouldCleanupRecorderPageAfterStop();\n\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] queue stop browser=${browserId} page=${recording.pageId} active=${recording.active} cleanupUi=${cleanupRecorderPage}`,\n );\n }\n\n let stopResult: ExtensionTaskResult;\n try {\n stopResult = await this.extensionTaskQueue.queueTask(\n 'browser_stop_recording',\n { pageId: recording.pageId, cleanupUi: cleanupRecorderPage },\n DEFAULT_TOOL_TIMEOUT_MS,\n browserId,\n );\n } finally {\n recording.active = false;\n }\n\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] stop result browser=${browserId} success=${stopResult?.success === true} isError=${stopResult?.result?.isError === true}`,\n );\n }\n\n let payload: { videoBase64?: string };\n try {\n payload = this.parseExtensionRecordingPayload(stopResult);\n } catch (error) {\n const persistedFromChunks = await this.persistExtensionRecordingArtifact(browserId);\n if (persistedFromChunks) {\n return;\n }\n throw error;\n }\n\n if (payload.videoBase64) {\n fs.mkdirSync(path.dirname(recording.outputPath), { recursive: true });\n fs.writeFileSync(recording.outputPath, Buffer.from(payload.videoBase64, 'base64'));\n this.clearExtensionRecordingChunkState(recording);\n recording.active = false;\n return;\n }\n\n const persistedFromChunks = await this.persistExtensionRecordingArtifact(browserId);\n if (!persistedFromChunks) {\n throw new Error(`Extension recording for browser \"${browserId}\" stopped without video data`);\n }\n }\n\n async stopPageRecording(\n browserId: string,\n pageId?: string,\n options: { includeBase64?: boolean } = {},\n ): Promise<{ outputPath: string; fileSizeBytes: number; videoBase64?: string }> {\n const browserInstance = this.browsers.get(browserId);\n const recording = browserInstance?.extensionRecording ?? this.closingExtensionRecordings.get(browserId);\n if (!recording) {\n throw new Error(`Browser \"${browserId}\" has no recording session`);\n }\n\n if (pageId && recording.pageId !== pageId) {\n throw new Error(`Page \"${pageId}\" does not match the active recording for browser \"${browserId}\"`);\n }\n\n this.closingExtensionRecordings.set(browserId, recording);\n\n let recordingError: Error | undefined;\n try {\n const finalizationResult = await this.attemptExtensionRecordingFinalization(browserId);\n recordingError = finalizationResult.error;\n } catch (error) {\n recordingError = error instanceof Error ? error : new Error(String(error));\n }\n\n if (!fs.existsSync(recording.outputPath)) {\n const streamedArtifactState = await this.waitForExtensionRecordingArtifactOrChunks(recording);\n let artifactArrived = streamedArtifactState.artifactReady;\n\n if (!artifactArrived && streamedArtifactState.chunkReady) {\n const finalizedFromChunks = await this.persistExtensionRecordingArtifact(browserId);\n artifactArrived = finalizedFromChunks || fs.existsSync(recording.outputPath);\n }\n\n if (!artifactArrived && recordingError) {\n throw recordingError;\n }\n }\n\n if (!fs.existsSync(recording.outputPath)) {\n throw new Error(`Recording artifact for browser \"${browserId}\" was not written to \"${recording.outputPath}\"`);\n }\n\n const fileBuffer = fs.readFileSync(recording.outputPath);\n this.closingExtensionRecordings.delete(browserId);\n return {\n outputPath: recording.outputPath,\n fileSizeBytes: fileBuffer.byteLength,\n ...(options.includeBase64 ? { videoBase64: fileBuffer.toString('base64') } : {}),\n };\n }\n\n async persistExtensionRecordingArtifact(browserId: string, videoBase64?: string): Promise<boolean> {\n const recording = this.getExtensionRecordingSession(browserId);\n if (!recording) {\n return false;\n }\n\n if (!videoBase64 && this.hasPersistedExtensionRecordingArtifact(recording)) {\n recording.active = false;\n this.closingExtensionRecordings.delete(browserId);\n return true;\n }\n\n fs.mkdirSync(path.dirname(recording.outputPath), { recursive: true });\n if (videoBase64) {\n fs.writeFileSync(recording.outputPath, Buffer.from(videoBase64, 'base64'));\n this.clearExtensionRecordingChunkState(recording);\n } else if (!this.persistExtensionRecordingChunksToArtifact(recording)) {\n return false;\n }\n\n recording.active = false;\n this.closingExtensionRecordings.delete(browserId);\n return true;\n }\n\n async persistExtensionRecordingChunk(\n browserId: string,\n chunkBase64: string,\n ): Promise<{ chunkBytes: number; totalBytes: number; chunkCount: number } | null> {\n const recording = this.getExtensionRecordingSession(browserId);\n if (!recording || !chunkBase64) {\n return null;\n }\n\n const chunkBuffer = Buffer.from(chunkBase64, 'base64');\n if (chunkBuffer.length === 0) {\n return null;\n }\n\n fs.mkdirSync(path.dirname(recording.chunkPath), { recursive: true });\n fs.appendFileSync(recording.chunkPath, chunkBuffer);\n recording.chunkCount += 1;\n recording.chunkBytes += chunkBuffer.length;\n\n return {\n chunkBytes: chunkBuffer.length,\n totalBytes: recording.chunkBytes,\n chunkCount: recording.chunkCount,\n };\n }\n\n /**\n * Enforces the maximum page limit per browser by closing the oldest page if limit is reached.\n */\n private async enforceMaxPageLimit(browserInstance: BrowserInstance): Promise<void> {\n if (browserInstance.pageIds.size < BrowserService.MAX_PAGES_PER_BROWSER) {\n return;\n }\n\n const pages = this.pageRegistry.findByBrowser(browserInstance.id);\n pages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const oldest = pages[0];\n if (!oldest) {\n return;\n }\n\n if (oldest.page) {\n await oldest.page.close();\n } else {\n this.pageRegistry.remove(oldest.id);\n browserInstance.pageIds.delete(oldest.id);\n }\n }\n\n /**\n * Enforces the maximum browser limit by closing the oldest browser if limit is reached.\n * Keeps only the 15 most recent browsers active.\n */\n private async enforceMaxBrowserLimit(): Promise<void> {\n if (this.browsers.size >= BrowserService.MAX_ACTIVE_BROWSERS) {\n // Find the oldest browser by creation timestamp\n const browsersArray = Array.from(this.browsers.values());\n browsersArray.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const oldestBrowser = browsersArray[0];\n if (oldestBrowser) {\n await this.closeBrowser(oldestBrowser.id);\n }\n }\n }\n\n /**\n * Check if a browser instance is still alive and connected.\n * Handles both playwright mode and extension mode browsers.\n */\n private isBrowserAlive(browserInstance: BrowserInstance): boolean {\n try {\n if (browserInstance.mode === 'playwright') {\n // For playwright mode, check if browser is connected\n return browserInstance.browser?.isConnected() ?? false;\n }\n if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n // For extension/vm mode, check PID first (most reliable for killed processes)\n if (!browserInstance.pid) {\n return false;\n }\n if (!this.isProcessRunning(browserInstance.pid)) {\n return false;\n }\n\n // Context may not exist for extension/vm launches; skip context check when missing\n if (!browserInstance.context) {\n return true;\n }\n try {\n // Try to access pages - will throw if context is closed\n browserInstance.context.pages();\n return true;\n } catch {\n return false;\n }\n }\n return false;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a process is running by PID\n */\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n return err.code === 'EPERM';\n }\n }\n\n private captureProcessOutput(): {\n attach(process: ChildProcess): void;\n snapshot(): string;\n } {\n const chunks: string[] = [];\n let totalBytes = 0;\n\n const push = (source: 'stdout' | 'stderr', chunk: unknown): void => {\n if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) {\n return;\n }\n\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n if (!text) {\n return;\n }\n\n const remainingBytes = PROCESS_OUTPUT_CAPTURE_LIMIT - totalBytes;\n if (remainingBytes <= 0) {\n return;\n }\n\n const slice = Buffer.from(text, 'utf8').subarray(0, remainingBytes).toString('utf8');\n totalBytes += Buffer.byteLength(slice, 'utf8');\n chunks.push(`[${source}] ${slice}`);\n };\n\n return {\n attach: (process: ChildProcess) => {\n process.stdout?.on('data', (chunk) => push('stdout', chunk));\n process.stderr?.on('data', (chunk) => push('stderr', chunk));\n },\n snapshot: () => chunks.join(''),\n };\n }\n\n private async startExtensionBrowser(input: {\n browserId: string;\n launchMode: BrowserMode;\n profileName?: string;\n vmRuntime: ResolvedVmRuntime;\n spawnArgs: string[];\n recordVideo?: VideoRecordingOptions;\n startupDelayMs?: number;\n launchUrl?: string;\n }): Promise<ExtensionBrowserLaunchResult> {\n const { browserId, launchMode, profileName, vmRuntime, spawnArgs, recordVideo, startupDelayMs, launchUrl } = input;\n const processOutput = this.captureProcessOutput();\n\n const chromeProcess = spawn(vmRuntime.command, spawnArgs, {\n detached: false,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: vmRuntime.kind === 'docker' ? process.env : { ...process.env, DISPLAY: process.env.DISPLAY },\n });\n\n if (!chromeProcess.pid) {\n throw new Error('Failed to start Chrome process');\n }\n\n processOutput.attach(chromeProcess);\n\n const extNow = new Date();\n const browserInstance: BrowserInstance = {\n id: browserId,\n mode: launchMode,\n browser: undefined,\n context: undefined,\n profileName,\n pageIds: new Set(),\n currentPageId: null,\n pid: chromeProcess.pid,\n createdAt: extNow,\n lastAccessedAt: extNow,\n };\n\n chromeProcess.on('exit', () => this.cleanupStaleBrowser(browserId, profileName));\n chromeProcess.on('error', () => this.cleanupStaleBrowser(browserId, profileName));\n\n this.browsers.set(browserId, browserInstance);\n if (profileName) {\n this.profileToBrowserId.set(profileName, browserId);\n }\n\n const pageId = this.pageRegistry.registerExtensionPage(browserId);\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n\n try {\n await this.waitForExtensionReady(browserId, chromeProcess, processOutput, {\n vmRuntimeKind: vmRuntime.kind,\n launchUrl,\n });\n\n if (recordVideo) {\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] initialize recording browser=${browserInstance.id} page=${pageId} dir=${recordVideo.dir}`,\n );\n }\n this.initializeExtensionRecording(browserInstance, pageId, recordVideo);\n const recording = browserInstance.extensionRecording;\n if (recording) {\n void this.startExtensionRecording(browserInstance, recording).catch((error) => {\n console.warn(\n `[BrowserService] Failed to eagerly start extension recording for browser \"${browserInstance.id}\" on page \"${pageId}\":`,\n error,\n );\n });\n }\n }\n } catch (error) {\n await this.closeBrowser(browserId).catch(() => {\n // Best-effort cleanup for failed launches.\n });\n throw error;\n }\n\n if (startupDelayMs && startupDelayMs > 0) {\n await this.delay(startupDelayMs);\n }\n\n return { browserInstance, pageId, process: chromeProcess };\n }\n\n private async waitForExtensionReady(\n browserId: string,\n chromeProcess: ChildProcess,\n processOutput: { snapshot(): string },\n context: {\n vmRuntimeKind: ResolvedVmRuntime['kind'];\n launchUrl?: string;\n },\n ): Promise<void> {\n if (!this.webSocketHub && !this.extensionSessionRegistry) {\n return;\n }\n\n let exitCode: number | null = null;\n let exitSignal: NodeJS.Signals | null = null;\n let processError: Error | undefined;\n\n chromeProcess.once('exit', (code, signal) => {\n exitCode = code;\n exitSignal = signal;\n });\n chromeProcess.once('error', (error) => {\n processError = error;\n });\n\n let stableSince: number | null = null;\n let sawConnection = false;\n let connectionFlaps = 0;\n let longestStableConnectionMs = 0;\n let firstConnectedAt: number | null = null;\n let graceApplied = false;\n const startedAt = Date.now();\n const startupTimeoutMs = this.getExtensionStartupTimeoutMs(context.vmRuntimeKind);\n const startupGraceTimeoutMs = this.getExtensionStartupGraceTimeoutMs(context.vmRuntimeKind);\n const readyStabilityMs = this.getExtensionReadyStabilityMs();\n const startupPollIntervalMs = this.getExtensionStartupPollIntervalMs();\n let deadline = startedAt + startupTimeoutMs;\n\n while (true) {\n const now = Date.now();\n if (now >= deadline) {\n if (!graceApplied && processError === undefined && exitCode === null && exitSignal === null) {\n graceApplied = true;\n deadline = now + startupGraceTimeoutMs;\n continue;\n }\n break;\n }\n\n const readyState = this.getExtensionReadyState(browserId);\n if (readyState.connected) {\n sawConnection = true;\n if (readyState.explicit) {\n return;\n }\n stableSince ??= now;\n firstConnectedAt ??= now;\n longestStableConnectionMs = Math.max(longestStableConnectionMs, now - stableSince);\n if (now - stableSince >= readyStabilityMs) {\n return;\n }\n } else {\n if (stableSince !== null) {\n connectionFlaps += 1;\n longestStableConnectionMs = Math.max(longestStableConnectionMs, now - stableSince);\n }\n stableSince = null;\n }\n\n if (processError || exitCode !== null || exitSignal !== null) {\n break;\n }\n\n await new Promise((resolve) => setTimeout(resolve, startupPollIntervalMs));\n }\n\n const finalReadyState = this.getExtensionReadyState(browserId);\n if (\n finalReadyState.connected &&\n (finalReadyState.explicit || (stableSince !== null && Date.now() - stableSince >= readyStabilityMs))\n ) {\n return;\n }\n\n const output = processOutput.snapshot();\n const logPath = this.writeLaunchFailureLog(browserId, output);\n const details = [\n `Chrome extension browser \"${browserId}\" failed to become ready within ${startupTimeoutMs + (graceApplied ? startupGraceTimeoutMs : 0)}ms.`,\n `Ready state requires a stable session for ${readyStabilityMs}ms.`,\n `Observed connection: ${sawConnection ? 'yes' : 'no'}.`,\n sawConnection && firstConnectedAt !== null\n ? `First connection observed after ${Math.max(0, firstConnectedAt - startedAt)}ms.`\n : undefined,\n `Longest stable connection: ${longestStableConnectionMs}ms.`,\n connectionFlaps > 0 ? `Connection flaps: ${connectionFlaps}.` : undefined,\n graceApplied ? `Startup grace applied: ${startupGraceTimeoutMs}ms.` : undefined,\n context.launchUrl ? `Launch URL: ${context.launchUrl}.` : undefined,\n processError ? `Error: ${processError.message}` : undefined,\n exitCode !== null ? `Exit code: ${exitCode}` : undefined,\n exitSignal ? `Signal: ${exitSignal}` : undefined,\n logPath ? `Launch log: ${logPath}` : undefined,\n ]\n .filter(Boolean)\n .join(' ');\n\n throw new Error(details);\n }\n\n private getExtensionStartupTimeoutMs(vmRuntimeKind?: ResolvedVmRuntime['kind']): number {\n return vmRuntimeKind === 'docker' ? DOCKER_EXTENSION_STARTUP_TIMEOUT_MS : EXTENSION_STARTUP_TIMEOUT_MS;\n }\n\n private getExtensionStartupGraceTimeoutMs(vmRuntimeKind?: ResolvedVmRuntime['kind']): number {\n return vmRuntimeKind === 'docker' ? DOCKER_EXTENSION_STARTUP_GRACE_TIMEOUT_MS : EXTENSION_STARTUP_GRACE_TIMEOUT_MS;\n }\n\n private getExtensionReadyStabilityMs(): number {\n return EXTENSION_READY_STABILITY_MS;\n }\n\n private getExtensionStartupPollIntervalMs(): number {\n return EXTENSION_STARTUP_POLL_INTERVAL_MS;\n }\n\n private isRetryableExtensionLaunchError(error: Error): boolean {\n return /failed to become ready/i.test(error.message) || /Failed to start Chrome process/i.test(error.message);\n }\n\n private shouldSerializeChromeForTestingLaunch(vmRuntime: ResolvedVmRuntime): boolean {\n return process.platform === 'darwin' && vmRuntime.kind === 'host' && this.isChromeTestingAlias(vmRuntime.command);\n }\n\n private async runSerializedChromeForTestingLaunch<T>(operation: () => Promise<T>): Promise<T> {\n let releaseQueue: (() => void) | undefined;\n const previousLaunch = BrowserService.chromeForTestingLaunchQueue;\n BrowserService.chromeForTestingLaunchQueue = new Promise<void>((resolve) => {\n releaseQueue = resolve;\n });\n\n await previousLaunch;\n\n try {\n return await operation();\n } finally {\n releaseQueue?.();\n }\n }\n\n private async delay(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private getExtensionReadyState(browserId: string): { connected: boolean; explicit: boolean } {\n if (this.extensionSessionRegistry) {\n return {\n connected: Boolean(this.extensionSessionRegistry.getSessionByBrowserId(browserId)),\n explicit: true,\n };\n }\n\n return {\n connected: Boolean(this.webSocketHub?.hasConnection(browserId)),\n explicit: false,\n };\n }\n\n private hasExtensionConnection(browserId: string): boolean {\n return this.getExtensionReadyState(browserId).connected;\n }\n\n private canReuseExtensionBrowser(browserInstance: BrowserInstance): boolean {\n if ((browserInstance.mode !== 'extension' && browserInstance.mode !== 'vm') || !browserInstance.pid) {\n return false;\n }\n\n return this.isProcessRunning(browserInstance.pid) && this.hasExtensionConnection(browserInstance.id);\n }\n\n private getTrackedExtensionPageId(browserInstance: BrowserInstance): string {\n const currentPageId =\n browserInstance.currentPageId && this.pageRegistry.get(browserInstance.currentPageId)\n ? browserInstance.currentPageId\n : Array.from(browserInstance.pageIds).find((pageId) => this.pageRegistry.get(pageId));\n\n if (currentPageId) {\n browserInstance.currentPageId = currentPageId;\n return currentPageId;\n }\n\n const pageId = this.pageRegistry.registerExtensionPage(browserInstance.id);\n browserInstance.pageIds.add(pageId);\n browserInstance.currentPageId = pageId;\n return pageId;\n }\n\n private writeLaunchFailureLog(browserId: string, output: string): string | undefined {\n if (!output.trim()) {\n return undefined;\n }\n\n try {\n const logPath = path.join(os.tmpdir(), `${browserId}-launch.log`);\n fs.writeFileSync(logPath, output, 'utf8');\n return logPath;\n } catch {\n return undefined;\n }\n }\n\n private async prepareDockerRetryUserDataDir(userDataDir: string): Promise<string> {\n await this.delay(DOCKER_RETRY_CLEANUP_DELAY_MS);\n\n try {\n const lockManagerCleaned = await this.lockManager.cleanupOrphanedLocks(userDataDir);\n this.cleanupExtensionSingletonArtifacts(userDataDir);\n return `${lockManagerCleaned ? 'cleaned' : 'checked'} ${userDataDir}`;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.cleanupExtensionSingletonArtifacts(userDataDir);\n return `lock-manager cleanup failed (${message}); singleton artifacts removed directly from ${userDataDir}`;\n }\n }\n\n private cleanupExtensionSingletonArtifacts(userDataDir: string): void {\n const artifactPaths = [\n path.join(userDataDir, 'SingletonLock'),\n path.join(userDataDir, 'SingletonCookie'),\n path.join(userDataDir, 'SingletonSocket'),\n path.join(userDataDir, 'lockfile'),\n path.join(userDataDir, 'Default', 'LOCK'),\n ];\n\n for (const artifactPath of artifactPaths) {\n try {\n fs.rmSync(artifactPath, { force: true });\n } catch {\n // Ignore cleanup errors. Retry path is best-effort.\n }\n }\n }\n\n /**\n * Clean up stale browser references from maps and registry.\n * Called when a browser is detected as dead/disconnected.\n * Also cleans up orphaned lock files for extension mode browsers.\n */\n private cleanupStaleBrowser(browserId: string, profileName?: string): void {\n if (profileName) {\n const userDataDir = this.getExtensionUserDataDir(profileName);\n this.cleanupExtensionSingletonArtifacts(userDataDir);\n this.lockManager.cleanupOrphanedLocks(userDataDir).catch(() => {\n // Ignore cleanup errors\n });\n }\n\n this.browsers.delete(browserId);\n if (profileName) {\n this.profileToBrowserId.delete(profileName);\n }\n // Note: PageRegistry cleanup is handled by page close handlers\n }\n\n private initializeExtensionRecording(\n browserInstance: BrowserInstance,\n pageId: string,\n recordVideo: VideoRecordingOptions,\n ): void {\n const outputPath = path.join(\n recordVideo.dir,\n `recording-${this.toSafeRecordingId(browserInstance.id)}-${browserInstance.createdAt.getTime()}.webm`,\n );\n this.initializeExtensionRecordingAtPath(browserInstance, pageId, outputPath);\n }\n\n private initializeExtensionRecordingAtPath(\n browserInstance: BrowserInstance,\n pageId: string,\n outputPath: string,\n ): void {\n browserInstance.extensionRecording = {\n pageId,\n outputPath,\n chunkPath: `${outputPath}.part`,\n active: false,\n chunkCount: 0,\n chunkBytes: 0,\n };\n }\n\n private async startExtensionRecording(\n browserInstance: BrowserInstance,\n recording: ExtensionRecordingSession,\n ): Promise<void> {\n if (!this.extensionTaskQueue) {\n throw new Error(`Extension recording task queue is unavailable for browser \"${browserInstance.id}\"`);\n }\n\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] queue start browser=${browserInstance.id} page=${recording.pageId} active=${recording.active}`,\n );\n }\n\n const result = await this.extensionTaskQueue.queueTask(\n 'browser_start_recording',\n { pageId: recording.pageId },\n DEFAULT_TOOL_TIMEOUT_MS,\n browserInstance.id,\n );\n\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] start result browser=${browserInstance.id} success=${result?.success === true} isError=${result?.result?.isError === true}`,\n );\n }\n\n if (!result.success || result.result?.isError) {\n const errorMessage = this.getExtensionTaskError(result);\n if (this.isExtensionRecordingAlreadyActiveError(errorMessage)) {\n recording.active = true;\n return;\n }\n\n throw new Error(errorMessage ?? `Failed to start extension recording for \"${browserInstance.id}\"`);\n }\n\n recording.active = true;\n }\n\n private isExtensionRecordingAlreadyActiveError(errorMessage?: string): boolean {\n if (!errorMessage) {\n return false;\n }\n\n return /active stream/i.test(errorMessage) || /recording already in progress/i.test(errorMessage);\n }\n\n private async attemptExtensionRecordingFinalization(\n browserId: string,\n ): Promise<{ completed: boolean; error?: Error }> {\n const finalizationPromise = this.finalizeExtensionRecording(browserId)\n .then(() => ({ completed: true as const }))\n .catch((error) => ({\n completed: true as const,\n error: error instanceof Error ? error : new Error(String(error)),\n }));\n\n const timeoutPromise = this.delay(EXTENSION_RECORDING_FINALIZE_TIMEOUT_MS).then(() => ({\n completed: false as const,\n }));\n return Promise.race([finalizationPromise, timeoutPromise]);\n }\n\n private async waitForExtensionRecordingArtifact(outputPath: string): Promise<boolean> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < EXTENSION_RECORDING_ARTIFACT_WAIT_MS) {\n try {\n const stats = fs.statSync(outputPath);\n if (stats.isFile() && stats.size > 0) {\n return true;\n }\n } catch {\n // File not ready yet.\n }\n\n await this.delay(EXTENSION_RECORDING_ARTIFACT_POLL_MS);\n }\n\n return false;\n }\n\n private async waitForExtensionRecordingArtifactOrChunks(\n recording: ExtensionRecordingSession,\n ): Promise<{ artifactReady: boolean; chunkReady: boolean }> {\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < EXTENSION_RECORDING_ARTIFACT_WAIT_MS) {\n try {\n const artifactStats = fs.statSync(recording.outputPath);\n if (artifactStats.isFile() && artifactStats.size > 0) {\n return { artifactReady: true, chunkReady: false };\n }\n } catch {\n // Artifact not ready yet.\n }\n\n try {\n const chunkStats = fs.statSync(recording.chunkPath);\n if (chunkStats.isFile() && chunkStats.size > 0) {\n return { artifactReady: false, chunkReady: true };\n }\n } catch {\n // Chunks not ready yet.\n }\n\n await this.delay(EXTENSION_RECORDING_ARTIFACT_POLL_MS);\n }\n\n return { artifactReady: false, chunkReady: false };\n }\n\n private getExtensionRecordingSession(browserId: string): ExtensionRecordingSession | undefined {\n return this.browsers.get(browserId)?.extensionRecording ?? this.closingExtensionRecordings.get(browserId);\n }\n\n private async finalizeClosingExtensionRecording(browserId: string): Promise<void> {\n const recording = this.closingExtensionRecordings.get(browserId);\n if (!recording) {\n return;\n }\n\n try {\n const artifactState = await this.waitForExtensionRecordingArtifactOrChunks(recording);\n if (!artifactState.artifactReady && artifactState.chunkReady) {\n await this.persistExtensionRecordingArtifact(browserId);\n }\n } finally {\n this.closingExtensionRecordings.delete(browserId);\n }\n }\n\n private shouldCloseRecordedExtensionPageFirst(browserInstance: BrowserInstance): boolean {\n return (\n (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') &&\n !!browserInstance.extensionRecording &&\n !!this.extensionTaskQueue\n );\n }\n\n private async closeRecordedExtensionPage(\n browserInstance: BrowserInstance,\n recording: ExtensionRecordingSession,\n ): Promise<void> {\n if (!this.extensionTaskQueue) {\n throw new Error(`Extension recording task queue is unavailable for browser \"${browserInstance.id}\"`);\n }\n\n if (browserInstance.pageIds.size <= 1) {\n const keepAliveResult = await this.extensionTaskQueue.queueTask(\n 'browser_new_page',\n { url: 'about:blank', setAsCurrent: false },\n DEFAULT_TOOL_TIMEOUT_MS,\n browserInstance.id,\n );\n\n if (!keepAliveResult.success || keepAliveResult.result?.isError) {\n throw new Error(\n this.getExtensionTaskError(keepAliveResult) ??\n `Failed to create a keepalive tab for browser \"${browserInstance.id}\" before closing the recorded page`,\n );\n }\n }\n\n const result = await this.extensionTaskQueue.queueTask(\n 'browser_close_page',\n { pageId: recording.pageId },\n DEFAULT_TOOL_TIMEOUT_MS,\n browserInstance.id,\n );\n\n if (!result.success || result.result?.isError) {\n throw new Error(\n this.getExtensionTaskError(result) ??\n `Failed to close the recorded page \"${recording.pageId}\" for browser \"${browserInstance.id}\"`,\n );\n }\n }\n\n private parseExtensionRecordingPayload(result: ExtensionTaskResult): { videoBase64?: string } {\n if (!result.success || result.result?.isError) {\n throw new Error(this.getExtensionTaskError(result) ?? 'Extension recording task failed');\n }\n\n const firstContent = result.result?.content[0];\n const text = firstContent?.type === 'text' ? firstContent.text : undefined;\n if (!text) {\n return {};\n }\n\n try {\n return JSON.parse(text) as { videoBase64?: string };\n } catch {\n throw new Error('Extension recording task returned invalid JSON payload');\n }\n }\n\n private getExtensionTaskError(result: ExtensionTaskResult): string | undefined {\n if (result.error) {\n return result.error;\n }\n\n const firstContent = result.result?.content[0];\n if (firstContent?.type === 'text') {\n return firstContent.text;\n }\n\n return undefined;\n }\n\n private toSafeRecordingId(browserId: string): string {\n return browserId.replace(/[^a-zA-Z0-9_-]+/g, '-');\n }\n\n private persistExtensionRecordingChunksToArtifact(recording: ExtensionRecordingSession): boolean {\n try {\n const chunkStats = fs.statSync(recording.chunkPath);\n if (!chunkStats.isFile() || chunkStats.size <= 0) {\n return false;\n }\n } catch {\n return false;\n }\n\n if (fs.existsSync(recording.outputPath)) {\n const outputStats = fs.statSync(recording.outputPath);\n if (outputStats.isFile() && outputStats.size > 0) {\n this.clearExtensionRecordingChunkState(recording);\n return true;\n }\n }\n\n fs.renameSync(recording.chunkPath, recording.outputPath);\n recording.chunkCount = 0;\n recording.chunkBytes = 0;\n return true;\n }\n\n private hasPersistedExtensionRecordingArtifact(recording: ExtensionRecordingSession): boolean {\n try {\n const outputStats = fs.statSync(recording.outputPath);\n return outputStats.isFile() && outputStats.size > 0;\n } catch {\n return false;\n }\n }\n\n private clearExtensionRecordingChunkState(recording: ExtensionRecordingSession): void {\n recording.chunkCount = 0;\n recording.chunkBytes = 0;\n\n try {\n if (fs.existsSync(recording.chunkPath)) {\n fs.rmSync(recording.chunkPath, { force: true });\n }\n } catch {\n // Best-effort cleanup only.\n }\n }\n\n private getBrowserType(type: 'chromium' | 'firefox' | 'webkit'): BrowserType {\n switch (type) {\n case 'chromium':\n return chromium;\n case 'firefox':\n return firefox;\n case 'webkit':\n return webkit;\n }\n }\n\n private buildContextOptions(\n profile: BrowserProfile | null,\n baseURL?: string,\n recordVideo?: VideoRecordingOptions,\n ): NonNullable<Parameters<Browser['newContext']>[0]> {\n const options: NonNullable<Parameters<Browser['newContext']>[0]> = {};\n\n if (baseURL) {\n options.baseURL = baseURL;\n }\n\n if (recordVideo) {\n options.recordVideo = recordVideo;\n }\n\n if (!profile) {\n return options;\n }\n\n if (profile.viewport) {\n options.viewport = profile.viewport;\n }\n\n if (profile.userAgent) {\n options.userAgent = profile.userAgent;\n }\n\n if (profile.locale) {\n options.locale = profile.locale;\n }\n\n if (profile.timezone) {\n options.timezoneId = profile.timezone;\n }\n\n if (profile.geolocation) {\n options.geolocation = profile.geolocation;\n }\n\n if (profile.permissions) {\n options.permissions = profile.permissions;\n }\n\n if (profile.colorScheme) {\n options.colorScheme = profile.colorScheme;\n }\n\n return options;\n }\n}\n","import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { stripTypeScriptTypes } from 'node:module';\n\nexport interface SavedCodeSnippet {\n name: string;\n description: string;\n snippetPath: string;\n}\n\nexport interface LoadedCodeSnippet extends SavedCodeSnippet {\n run: (context: { page: unknown; context: unknown; browser: unknown }) => unknown;\n}\n\ninterface LoadedSnippetModule {\n name?: unknown;\n description?: unknown;\n run?: unknown;\n}\n\nexport interface ICodeSnippetService {\n getDirectory(): string | undefined;\n listSnippets(): Promise<SavedCodeSnippet[]>;\n loadSnippet(snippetPath: string): Promise<LoadedCodeSnippet>;\n saveSnippet(input: { name: string; description: string; code: string }): Promise<SavedCodeSnippet>;\n}\n\nfunction sanitizeSnippetName(name: string): string {\n const sanitized = name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return sanitized || 'snippet';\n}\n\nfunction indentCode(code: string): string {\n return code\n .replace(/\\r\\n/g, '\\n')\n .split('\\n')\n .map((line) => (line.length > 0 ? ` ${line}` : line))\n .join('\\n');\n}\n\nfunction buildSnippetSource(input: { name: string; description: string; code: string }): string {\n return [\n `export const name = ${JSON.stringify(input.name)};`,\n `export const description = ${JSON.stringify(input.description)};`,\n 'export const run = async ({ page, context, browser }) => {',\n indentCode(input.code),\n '};',\n '',\n ].join('\\n');\n}\n\nasync function loadSnippetModule(filePath: string): Promise<LoadedSnippetModule> {\n const source = await readFile(filePath, 'utf8');\n const compiledSource = stripTypeScriptTypes(source, { mode: 'strip' });\n const moduleUrl = `data:text/javascript;base64,${Buffer.from(compiledSource, 'utf8').toString('base64')}`;\n return (await import(moduleUrl)) as LoadedSnippetModule;\n}\n\nexport class CodeSnippetService implements ICodeSnippetService {\n constructor(private readonly snippetsDir = process.env.BROWSE_TOOL_SNIPPETS_DIR) {}\n\n getDirectory(): string | undefined {\n return this.snippetsDir ? path.resolve(this.snippetsDir) : undefined;\n }\n\n async listSnippets(): Promise<SavedCodeSnippet[]> {\n const directory = this.requireDirectory();\n await mkdir(directory, { recursive: true });\n\n const entries = await readdir(directory, { withFileTypes: true });\n const snippets: SavedCodeSnippet[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.ts')) {\n continue;\n }\n\n const snippetPath = entry.name;\n const filePath = path.join(directory, entry.name);\n const loadedModule = await loadSnippetModule(filePath);\n const name =\n typeof loadedModule.name === 'string' && loadedModule.name.trim().length > 0\n ? loadedModule.name\n : entry.name.replace(/\\.ts$/, '');\n const description = typeof loadedModule.description === 'string' ? loadedModule.description : '';\n snippets.push({ name, description, snippetPath });\n }\n\n snippets.sort((left, right) => left.name.localeCompare(right.name));\n return snippets;\n }\n\n async loadSnippet(snippetPath: string): Promise<LoadedCodeSnippet> {\n const resolvedPath = await this.resolveSnippetPath(snippetPath);\n const loadedModule = await loadSnippetModule(resolvedPath.filePath);\n\n if (typeof loadedModule.run !== 'function') {\n throw new Error(`Snippet \"${resolvedPath.snippetPath}\" must export a \"run\" function`);\n }\n\n return {\n name:\n typeof loadedModule.name === 'string' && loadedModule.name.trim().length > 0\n ? loadedModule.name\n : resolvedPath.snippetPath.replace(/\\.ts$/, ''),\n description: typeof loadedModule.description === 'string' ? loadedModule.description : '',\n snippetPath: resolvedPath.snippetPath,\n run: loadedModule.run as LoadedCodeSnippet['run'],\n };\n }\n\n async saveSnippet(input: { name: string; description: string; code: string }): Promise<SavedCodeSnippet> {\n const directory = this.requireDirectory();\n await mkdir(directory, { recursive: true });\n\n const sanitizedName = sanitizeSnippetName(input.name);\n const snippetPath = `${sanitizedName}.ts`;\n const filePath = path.join(directory, snippetPath);\n\n await writeFile(filePath, buildSnippetSource(input), 'utf8');\n\n return {\n name: input.name,\n description: input.description,\n snippetPath,\n };\n }\n\n private requireDirectory(): string {\n const directory = this.getDirectory();\n if (!directory) {\n throw new Error('Snippet storage is not configured. Start browse-tool with --snippets-dir.');\n }\n return directory;\n }\n\n private async resolveSnippetPath(snippetPath: string): Promise<{ filePath: string; snippetPath: string }> {\n const directory = this.requireDirectory();\n const candidate = snippetPath.endsWith('.ts') ? snippetPath : `${snippetPath}.ts`;\n const resolvedPath = path.resolve(directory, candidate);\n\n if (!resolvedPath.startsWith(`${directory}${path.sep}`) && resolvedPath !== path.join(directory, candidate)) {\n throw new Error('Snippet path must stay within the configured snippets directory');\n }\n\n const fileStats = await stat(resolvedPath).catch(() => null);\n if (!fileStats?.isFile()) {\n throw new Error(`Snippet \"${snippetPath}\" not found`);\n }\n\n return {\n filePath: resolvedPath,\n snippetPath: path.relative(directory, resolvedPath),\n };\n }\n}\n","/**\n * ElementLocatorService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on element location)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { Locator, Page } from 'playwright';\n\n/**\n * Selector specification for locating elements.\n * Supports CSS selectors, XPath, text content, and accessibility snapshot UIDs.\n */\nexport interface ElementSelector {\n /** CSS selector (e.g., \"button.submit\", \"#login-form\") */\n selector?: string;\n /** XPath expression (e.g., \"//button[@type='submit']\") */\n xpath?: string;\n /** Text content to match (uses text= selector) */\n text?: string;\n /** Accessibility snapshot UID reference */\n uid?: string;\n /** Frame selector for iframe content (CSS or name) */\n frame?: string;\n}\n\nexport interface IElementLocatorService {\n locate(page: Page, spec: ElementSelector): Promise<Locator>;\n locateAll(page: Page, spec: ElementSelector): Promise<Locator>;\n waitForElement(page: Page, spec: ElementSelector, options?: WaitOptions): Promise<Locator>;\n}\n\nexport interface WaitOptions {\n timeout?: number;\n state?: 'attached' | 'detached' | 'visible' | 'hidden';\n}\n\n@injectable()\nexport class ElementLocatorService implements IElementLocatorService {\n /**\n * Locates a single element on the page using the provided selector specification.\n * Throws if no selector is provided or element is not found.\n *\n * @param page - The Playwright page to search in.\n * @param spec - The selector specification.\n * @returns A Locator for the matched element.\n */\n async locate(page: Page, spec: ElementSelector): Promise<Locator> {\n const context = await this.getFrameContext(page, spec.frame);\n const locator = this.buildLocator(context, spec);\n\n const count = await locator.count();\n if (count === 0) {\n throw new Error(`Element not found: ${this.describeSelector(spec)}`);\n }\n\n return locator.first();\n }\n\n /**\n * Returns a locator for all matching elements.\n *\n * @param page - The Playwright page to search in.\n * @param spec - The selector specification.\n * @returns A Locator for all matched elements.\n */\n async locateAll(page: Page, spec: ElementSelector): Promise<Locator> {\n const context = await this.getFrameContext(page, spec.frame);\n return this.buildLocator(context, spec);\n }\n\n /**\n * Waits for an element to appear and returns its locator.\n *\n * @param page - The Playwright page to search in.\n * @param spec - The selector specification.\n * @param options - Wait options (timeout, state).\n * @returns A Locator for the matched element.\n */\n async waitForElement(page: Page, spec: ElementSelector, options: WaitOptions = {}): Promise<Locator> {\n const context = await this.getFrameContext(page, spec.frame);\n const locator = this.buildLocator(context, spec);\n\n await locator.waitFor({\n timeout: options.timeout ?? DEFAULT_TOOL_TIMEOUT_MS,\n state: options.state ?? 'visible',\n });\n\n return locator.first();\n }\n\n /**\n * Gets the appropriate frame context for element location.\n */\n private async getFrameContext(page: Page, frameSelector?: string): Promise<Page | ReturnType<Page['frameLocator']>> {\n if (!frameSelector) {\n return page;\n }\n return page.frameLocator(frameSelector);\n }\n\n /**\n * Builds a Playwright locator from the selector specification.\n */\n private buildLocator(context: Page | ReturnType<Page['frameLocator']>, spec: ElementSelector): Locator {\n if (spec.uid) {\n // UID format: \"uid=<number>\" for accessibility tree reference\n return context.locator(`[data-uid=\"${spec.uid}\"]`);\n }\n\n if (spec.xpath) {\n return context.locator(`xpath=${spec.xpath}`);\n }\n\n if (spec.text) {\n return context.locator(`text=${spec.text}`);\n }\n\n if (spec.selector) {\n return context.locator(spec.selector);\n }\n\n throw new Error('No selector provided. Specify one of: selector, xpath, text, or uid.');\n }\n\n /**\n * Creates a human-readable description of the selector for error messages.\n */\n private describeSelector(spec: ElementSelector): string {\n const parts: string[] = [];\n\n if (spec.uid) parts.push(`uid=\"${spec.uid}\"`);\n if (spec.selector) parts.push(`selector=\"${spec.selector}\"`);\n if (spec.xpath) parts.push(`xpath=\"${spec.xpath}\"`);\n if (spec.text) parts.push(`text=\"${spec.text}\"`);\n if (spec.frame) parts.push(`frame=\"${spec.frame}\"`);\n\n return parts.length > 0 ? parts.join(', ') : 'empty selector';\n }\n}\n","/**\n * ExtensionPageProxy\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for Playwright API compatibility\n * - Service pattern for business logic encapsulation\n * - Adapter pattern to bridge extension and Playwright interfaces\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Match Playwright Page interface methods\n * - Route calls through ExtensionTaskQueue\n *\n * AVOID:\n * - Blocking operations\n * - Missing error handling\n * - Incomplete Playwright API coverage\n */\n\nimport 'reflect-metadata/lite';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { PAGE_PROXY_BRAND } from '../utils/extension-expect.js';\nimport type { ExtensionTaskQueue } from './ExtensionTaskQueue.js';\nimport { LocatorProxy } from './LocatorProxy.js';\n\n/**\n * Locator options for element selection\n */\nexport interface LocatorOptions {\n /** CSS selector, XPath, or text content */\n selector: string;\n /** Selector strategy */\n strategy?: 'css' | 'xpath' | 'text' | 'role' | 'uid';\n}\n\n/**\n * Click options\n */\nexport interface ClickOptions {\n /** Number of clicks */\n clickCount?: number;\n /** Delay between mousedown and mouseup */\n delay?: number;\n /** Mouse button */\n button?: 'left' | 'right' | 'middle';\n /** Modifier keys */\n modifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>;\n}\n\n/**\n * Fill options\n */\nexport interface FillOptions {\n /** Whether to clear existing content first */\n force?: boolean;\n}\n\n/**\n * Screenshot options\n */\nexport interface ScreenshotOptions {\n /** Screenshot path */\n path?: string;\n /** Full page screenshot */\n fullPage?: boolean;\n /** Image format */\n type?: 'png' | 'jpeg';\n}\n\n/**\n * Goto options\n */\nexport interface GotoOptions {\n /** Wait until event */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n/**\n * Wait for selector options\n */\nexport interface WaitForSelectorOptions {\n /** Wait for state */\n state?: 'attached' | 'detached' | 'visible' | 'hidden';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n/**\n * Proxy response from extension\n */\ninterface ProxyResponse {\n success: boolean;\n result?: unknown;\n error?: string;\n}\n\nconst EXTENSION_PROXY_DEBUG_ENABLED = process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS === '1';\n\nfunction summarizeArgs(args: Record<string, unknown>): string {\n return JSON.stringify(args, (_key, value) => {\n if (typeof value === 'string' && value.length > 240) {\n return `${value.slice(0, 240)}...<trimmed>`;\n }\n return value;\n });\n}\n\nfunction debugProxy(message: string, details?: Record<string, unknown>): void {\n if (!EXTENSION_PROXY_DEBUG_ENABLED) {\n return;\n }\n\n if (details) {\n console.error(`[ExtensionPageProxy] ${message}`, details);\n return;\n }\n\n console.error(`[ExtensionPageProxy] ${message}`);\n}\n\nfunction resolveProxyValue<T>(result: unknown): T | undefined {\n if (typeof result === 'object' && result !== null && 'value' in result) {\n return (result as { value?: T }).value;\n }\n return result as T;\n}\n\nexport interface IExtensionPageProxy {\n /** Set the target page for this proxy */\n setTarget(pageId: string, browserId: string): void;\n /** Get the current page ID */\n readonly pageId: string | undefined;\n /** Get the current browser ID */\n readonly browserId: string | undefined;\n goto(url: string, options?: GotoOptions): Promise<void>;\n click(selector: string, options?: ClickOptions): Promise<void>;\n fill(selector: string, value: string, options?: FillOptions): Promise<void>;\n type(selector: string, text: string, options?: { delay?: number }): Promise<void>;\n press(key: string): Promise<void>;\n hover(selector: string): Promise<void>;\n selectOption(selector: string, values: string | string[]): Promise<void>;\n waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise<void>;\n waitForTimeout(timeout: number): Promise<void>;\n screenshot(options?: ScreenshotOptions): Promise<string>;\n content(): Promise<string>;\n title(): Promise<string>;\n textContent(selector: string): Promise<string | null>;\n innerText(selector: string): Promise<string>;\n inputValue(selector: string): Promise<string>;\n url(): string;\n evaluate<T>(fn: string | ((...args: unknown[]) => T), ...args: unknown[]): Promise<T>;\n getSnapshot(): Promise<string>;\n /** Locator factory: find elements by ARIA role */\n getByRole(role: string, options?: { name?: string | RegExp; exact?: boolean }): LocatorProxy;\n /** Locator factory: find elements by text content */\n getByText(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy;\n /** Locator factory: find elements by associated label */\n getByLabel(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy;\n /** Locator factory: find elements by placeholder attribute */\n getByPlaceholder(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy;\n /** Locator factory: find elements by data-testid */\n getByTestId(testId: string): LocatorProxy;\n /** Locator factory: find elements by CSS selector */\n locator(selector: string): LocatorProxy;\n /** Returns a minimal browser context stub */\n context(): { clearCookies: () => Promise<void> };\n /** Reload the page */\n reload(options?: { waitUntil?: string; timeout?: number }): Promise<void>;\n /** Get current URL asynchronously from the page */\n currentUrlAsync(): Promise<string>;\n /** Stub for waitForResponse — falls back to waitForTimeout */\n waitForResponse(urlOrPredicate: unknown, options?: { timeout?: number }): Promise<void>;\n /** Get cookies and storage state for the current target page */\n getStorageState(): Promise<Record<string, unknown>>;\n /** Clear cookies and storage state for the current target page */\n clearStorageState(): Promise<void>;\n /** Start recording tab video via chrome.tabCapture */\n startRecording(): Promise<void>;\n /** Stop recording and return base64-encoded WebM video data */\n stopRecording(): Promise<string>;\n}\n\n@injectable()\nexport class ExtensionPageProxy implements IExtensionPageProxy {\n readonly [PAGE_PROXY_BRAND] = true;\n\n private currentUrl = 'about:blank';\n private readonly defaultTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS;\n private _pageId?: string;\n private _browserId?: string;\n\n constructor(@inject(PLAYWRIGHT_TYPES.ExtensionTaskQueue) private readonly taskQueue: ExtensionTaskQueue) {}\n\n /**\n * Set the target page for this proxy.\n * Must be called before using the proxy.\n */\n setTarget(pageId: string, browserId: string): void {\n this._pageId = pageId;\n this._browserId = browserId;\n }\n\n /**\n * Get the current page ID\n */\n get pageId(): string | undefined {\n return this._pageId;\n }\n\n /**\n * Get the current browser ID\n */\n get browserId(): string | undefined {\n return this._browserId;\n }\n\n /**\n * Navigate to URL\n */\n async goto(url: string, options?: GotoOptions): Promise<void> {\n const result = await this.executeTask('browser_navigate', {\n url,\n waitUntil: options?.waitUntil,\n timeout: options?.timeout,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Navigation failed');\n }\n\n this.currentUrl = url;\n }\n\n /**\n * Click an element\n */\n async click(selector: string, options?: ClickOptions): Promise<void> {\n const result = await this.executeTask('browser_click', {\n selector,\n clickCount: options?.clickCount,\n delay: options?.delay,\n button: options?.button,\n modifiers: options?.modifiers,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Click failed on ${selector}`);\n }\n }\n\n /**\n * Fill an input field\n */\n async fill(selector: string, value: string, options?: FillOptions): Promise<void> {\n const result = await this.executeTask('browser_fill', {\n selector,\n text: value,\n force: options?.force,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Fill failed on ${selector}`);\n }\n }\n\n /**\n * Type text character by character\n */\n async type(selector: string, text: string, options?: { delay?: number }): Promise<void> {\n const result = await this.executeTask('browser_type', {\n selector,\n text,\n delay: options?.delay,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Type failed on ${selector}`);\n }\n }\n\n /**\n * Press a key\n */\n async press(key: string): Promise<void> {\n const result = await this.executeTask('browser_press_key', {\n key,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Press key failed: ${key}`);\n }\n }\n\n /**\n * Hover over an element\n */\n async hover(selector: string): Promise<void> {\n const result = await this.executeTask('browser_hover', {\n selector,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Hover failed on ${selector}`);\n }\n }\n\n /**\n * Select options from a dropdown\n */\n async selectOption(selector: string, values: string | string[]): Promise<void> {\n const result = await this.executeTask('browser_select', {\n selector,\n values: Array.isArray(values) ? values : [values],\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Select option failed on ${selector}`);\n }\n }\n\n /**\n * Wait for a selector to match\n */\n async waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise<void> {\n const result = await this.executeTask('browser_wait_for', {\n selector,\n state: options?.state ?? 'visible',\n timeout: options?.timeout,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? `Wait for selector failed: ${selector}`);\n }\n }\n\n /**\n * Wait for a timeout\n */\n async waitForTimeout(timeout: number): Promise<void> {\n const result = await this.executeTask('browser_wait_for', {\n timeout,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Wait for timeout failed');\n }\n }\n\n /**\n * Take a screenshot\n */\n async screenshot(options?: ScreenshotOptions): Promise<string> {\n const result = await this.executeTask('browser_screenshot', {\n path: options?.path,\n fullPage: options?.fullPage,\n type: options?.type,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Screenshot failed');\n }\n\n return (result.result as { path?: string })?.path ?? '';\n }\n\n /**\n * Get page HTML content\n */\n async content(): Promise<string> {\n const result = await this.executeTask('browser_evaluate_script', {\n script: 'document.documentElement.outerHTML',\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Get content failed');\n }\n\n return resolveProxyValue<string>(result.result) ?? '';\n }\n\n /**\n * Get page title\n */\n async title(): Promise<string> {\n const result = await this.executeTask('browser_evaluate_script', {\n script: 'document.title',\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Get title failed');\n }\n\n return resolveProxyValue<string>(result.result) ?? '';\n }\n\n /**\n * Get text content for the first element matching a selector.\n */\n async textContent(selector: string): Promise<string | null> {\n return this.locator(selector).textContent();\n }\n\n /**\n * Get inner text for the first element matching a selector.\n */\n async innerText(selector: string): Promise<string> {\n return this.locator(selector).innerText();\n }\n\n /**\n * Get input value for the first element matching a selector.\n */\n async inputValue(selector: string): Promise<string> {\n return this.locator(selector).inputValue();\n }\n\n /**\n * Get current URL (synchronous)\n */\n url(): string {\n return this.currentUrl;\n }\n\n /**\n * Evaluate JavaScript in page context\n */\n async evaluate<T>(fn: string | ((...args: unknown[]) => T), ...args: unknown[]): Promise<T> {\n const script =\n typeof fn === 'function' ? `(${fn.toString()})(${args.map((a) => JSON.stringify(a)).join(',')})` : fn;\n\n const result = await this.executeTask('browser_evaluate_script', {\n script,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Evaluate failed');\n }\n\n return resolveProxyValue<T>(result.result) as T;\n }\n\n /**\n * Get accessibility snapshot\n */\n async getSnapshot(): Promise<string> {\n const result = await this.executeTask('browser_snapshot', {});\n\n if (!result.success) {\n throw new Error(result.error ?? 'Get snapshot failed');\n }\n\n const snapshot = resolveProxyValue<unknown>(result.result);\n if (typeof snapshot === 'string') {\n return snapshot;\n }\n if (snapshot === undefined || snapshot === null) {\n return '';\n }\n return JSON.stringify(snapshot, null, 2);\n }\n\n // ── Locator factory methods ─────────────────────────────────────\n\n getByRole(role: string, options?: { name?: string | RegExp; exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'role', role, options }]);\n }\n\n getByText(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'text', text, options }]);\n }\n\n getByLabel(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'label', text, options }]);\n }\n\n getByPlaceholder(text: string | RegExp, options?: { exact?: boolean }): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'placeholder', text, options }]);\n }\n\n getByTestId(testId: string): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'testId', testId }]);\n }\n\n locator(selector: string): LocatorProxy {\n return new LocatorProxy(this, [{ type: 'css', selector }]);\n }\n\n // ── Additional Playwright Page API methods ──────────────────────\n\n /**\n * Returns a minimal browser context stub with clearCookies support.\n */\n context(): { clearCookies: () => Promise<void> } {\n return {\n clearCookies: async () => {\n await this.clearStorageState();\n },\n };\n }\n\n /**\n * Reload the current page.\n */\n async reload(options?: { waitUntil?: string; timeout?: number }): Promise<void> {\n const result = await this.executeTask('browser_reload', {\n waitUntil: options?.waitUntil,\n timeout: options?.timeout,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Reload failed');\n }\n }\n\n /**\n * Get current URL asynchronously by evaluating window.location.href\n * in the page context. More accurate than url() after client-side navigation.\n */\n async currentUrlAsync(): Promise<string> {\n const href = await this.evaluate<string>('window.location.href');\n if (href) {\n this.currentUrl = href;\n }\n return this.currentUrl;\n }\n\n /**\n * Wait for a network response using the extension's DevTools-backed network listener.\n */\n async waitForResponse(urlOrPredicate: unknown, options?: { timeout?: number }): Promise<void> {\n const responseTarget =\n typeof urlOrPredicate === 'string'\n ? { url: urlOrPredicate }\n : urlOrPredicate instanceof RegExp\n ? { urlPattern: { source: urlOrPredicate.source, flags: urlOrPredicate.flags } }\n : null;\n\n if (!responseTarget) {\n throw new Error('Extension mode only supports waitForResponse with a string or RegExp target');\n }\n\n const result = await this.executeTask('browser_wait_for_response', {\n ...responseTarget,\n timeout: options?.timeout ?? this.defaultTimeoutMs,\n });\n\n if (!result.success) {\n throw new Error(result.error ?? 'Wait for response failed');\n }\n }\n\n /**\n * Read cookies and storage state for the current target page.\n */\n async getStorageState(): Promise<Record<string, unknown>> {\n const result = await this.executeTask('browser_get_storage_state', {});\n\n if (!result.success) {\n throw new Error(result.error ?? 'Get storage state failed');\n }\n\n return (result.result as Record<string, unknown>) ?? {};\n }\n\n /**\n * Clear cookies and storage state for the current target page.\n */\n async clearStorageState(): Promise<void> {\n const result = await this.executeTask('browser_clear_storage_state', {});\n\n if (!result.success) {\n throw new Error(result.error ?? 'Clear storage state failed');\n }\n }\n\n /**\n * Start recording tab video via chrome.tabCapture in the extension.\n */\n async startRecording(): Promise<void> {\n const result = await this.executeTask('browser_start_recording', {});\n\n if (!result.success) {\n throw new Error(result.error ?? 'Start recording failed');\n }\n }\n\n /**\n * Stop recording and return base64-encoded WebM video data.\n */\n async stopRecording(): Promise<string> {\n const result = await this.executeTask('browser_stop_recording', {});\n\n if (!result.success) {\n throw new Error(result.error ?? 'Stop recording failed');\n }\n\n return (result.result as { videoBase64?: string })?.videoBase64 ?? '';\n }\n\n /**\n * Execute a task through the extension task queue\n */\n private async executeTask(tool: string, args: Record<string, unknown>): Promise<ProxyResponse> {\n if (!this._pageId || !this._browserId) {\n return {\n success: false,\n error: 'Extension page proxy target is not initialized',\n };\n }\n\n const argsWithTarget = {\n ...args,\n pageId: this._pageId,\n };\n\n debugProxy('Queueing extension task', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n args: summarizeArgs(argsWithTarget),\n });\n\n let taskResult;\n try {\n taskResult = await this.taskQueue.queueTask(tool, argsWithTarget, this.defaultTimeoutMs, this._browserId);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n debugProxy('Extension task threw before result', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n error: message,\n });\n return {\n success: false,\n error: `Extension task \"${tool}\" threw for page \"${this._pageId}\" in browser \"${this._browserId}\": ${message}`,\n };\n }\n\n if (!taskResult.success) {\n debugProxy('Extension task returned unsuccessful result', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n taskError: taskResult.error ?? 'Task failed',\n taskResult,\n });\n return {\n success: false,\n error:\n taskResult.error ??\n `Extension task \"${tool}\" failed for page \"${this._pageId}\" in browser \"${this._browserId}\"`,\n };\n }\n\n const content = taskResult.result?.content?.[0];\n if (content?.type === 'text' && content.text) {\n try {\n const parsed = JSON.parse(content.text);\n debugProxy('Extension task completed with JSON text result', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n });\n return {\n success: true,\n result: parsed,\n };\n } catch {\n debugProxy('Extension task completed with plain text result', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n textPreview: content.text.slice(0, 240),\n });\n return {\n success: true,\n result: { value: content.text },\n };\n }\n }\n\n debugProxy('Extension task completed with non-text result payload', {\n tool,\n browserId: this._browserId,\n pageId: this._pageId,\n taskResult,\n });\n return {\n success: true,\n result: taskResult.result,\n };\n }\n}\n","/**\n * ExtensionSessionRegistry\n *\n * DESIGN PATTERNS:\n * - Service pattern for session management\n * - Registry pattern for tracking active browser sessions\n * - Observer pattern for session state changes\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Use Map for efficient session lookup\n *\n * AVOID:\n * - Unbounded session storage (implement cleanup)\n * - Missing timeout handling\n * - Memory leaks from uncleared sessions\n */\n\nimport 'reflect-metadata/lite';\nimport { injectable } from 'inversify';\n\n/**\n * Session control mode indicating who controls the browser\n */\nexport type SessionControlMode = 'spec' | 'ai' | 'manual';\n\n/**\n * Extension session representing a connected stealth browser\n */\nexport interface ExtensionSession {\n /** Unique session ID */\n id: string;\n /** Browser ID from StealthLauncher */\n browserId: string;\n /** Current tab/page ID in the extension */\n tabId?: number;\n /** Current URL */\n currentUrl?: string;\n /** Session creation timestamp */\n createdAt: Date;\n /** Last heartbeat timestamp */\n lastHeartbeatAt: Date;\n /** Current control mode */\n controlMode: SessionControlMode;\n /** Active spec path if running a spec */\n activeSpecPath?: string;\n /** Whether handoff to AI has been requested */\n handoffRequested: boolean;\n /** Metadata from extension */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Session registration request from extension\n */\nexport interface SessionRegistrationRequest {\n /** Browser ID from launch */\n browserId: string;\n /** Optional tab ID */\n tabId?: number;\n /** Initial URL */\n url?: string;\n /** Extension metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Heartbeat update from extension\n */\nexport interface SessionHeartbeatRequest {\n /** Session ID */\n sessionId: string;\n /** Current tab ID */\n tabId?: number;\n /** Current URL */\n url?: string;\n /** Additional state */\n state?: Record<string, unknown>;\n}\n\n/**\n * Handoff request for AI takeover\n */\nexport interface HandoffRequest {\n /** Session ID */\n sessionId: string;\n /** Reason for handoff */\n reason?: string;\n /** Current page state snapshot */\n pageState?: {\n url: string;\n title?: string;\n snapshot?: string;\n };\n}\n\nexport interface IExtensionSessionRegistry {\n register(request: SessionRegistrationRequest): ExtensionSession;\n heartbeat(request: SessionHeartbeatRequest): ExtensionSession | undefined;\n requestHandoff(request: HandoffRequest): ExtensionSession | undefined;\n acknowledgeHandoff(sessionId: string): ExtensionSession | undefined;\n getSession(sessionId: string): ExtensionSession | undefined;\n getSessionByBrowserId(browserId: string): ExtensionSession | undefined;\n listSessions(): ExtensionSession[];\n removeSession(sessionId: string): boolean;\n setControlMode(sessionId: string, mode: SessionControlMode): ExtensionSession | undefined;\n setActiveSpec(sessionId: string, specPath: string | undefined): ExtensionSession | undefined;\n cleanupStaleSessions(maxAgeMs?: number): number;\n}\n\n@injectable()\nexport class ExtensionSessionRegistry implements IExtensionSessionRegistry {\n private sessions = new Map<string, ExtensionSession>();\n private browserIdToSessionId = new Map<string, string>();\n private sessionIdCounter = 0;\n private readonly defaultStaleThresholdMs = 60000;\n\n /**\n * Register a new extension session\n */\n register(request: SessionRegistrationRequest): ExtensionSession {\n const existingSessionId = this.browserIdToSessionId.get(request.browserId);\n if (existingSessionId) {\n this.sessions.delete(existingSessionId);\n this.browserIdToSessionId.delete(request.browserId);\n }\n\n const sessionId = `session-${++this.sessionIdCounter}-${Date.now()}`;\n const now = new Date();\n\n const session: ExtensionSession = {\n id: sessionId,\n browserId: request.browserId,\n tabId: request.tabId,\n currentUrl: request.url,\n createdAt: now,\n lastHeartbeatAt: now,\n controlMode: 'manual',\n handoffRequested: false,\n metadata: request.metadata,\n };\n\n this.sessions.set(sessionId, session);\n this.browserIdToSessionId.set(request.browserId, sessionId);\n\n return session;\n }\n\n /**\n * Update session heartbeat\n */\n heartbeat(request: SessionHeartbeatRequest): ExtensionSession | undefined {\n const session = this.sessions.get(request.sessionId);\n if (!session) {\n return undefined;\n }\n\n session.lastHeartbeatAt = new Date();\n\n if (request.tabId !== undefined) {\n session.tabId = request.tabId;\n }\n\n if (request.url !== undefined) {\n session.currentUrl = request.url;\n }\n\n if (request.state) {\n session.metadata = { ...session.metadata, ...request.state };\n }\n\n return session;\n }\n\n /**\n * Request handoff from spec to AI control\n */\n requestHandoff(request: HandoffRequest): ExtensionSession | undefined {\n const session = this.sessions.get(request.sessionId);\n if (!session) {\n return undefined;\n }\n\n session.handoffRequested = true;\n\n if (request.pageState) {\n session.currentUrl = request.pageState.url;\n session.metadata = {\n ...session.metadata,\n handoffReason: request.reason,\n handoffPageState: request.pageState,\n };\n }\n\n return session;\n }\n\n /**\n * Acknowledge handoff and switch control to AI\n */\n acknowledgeHandoff(sessionId: string): ExtensionSession | undefined {\n const session = this.sessions.get(sessionId);\n if (!session || !session.handoffRequested) {\n return undefined;\n }\n\n session.handoffRequested = false;\n session.controlMode = 'ai';\n session.activeSpecPath = undefined;\n\n return session;\n }\n\n /**\n * Get session by ID\n */\n getSession(sessionId: string): ExtensionSession | undefined {\n return this.sessions.get(sessionId);\n }\n\n /**\n * Get session by browser ID\n */\n getSessionByBrowserId(browserId: string): ExtensionSession | undefined {\n const sessionId = this.browserIdToSessionId.get(browserId);\n if (!sessionId) {\n return undefined;\n }\n return this.sessions.get(sessionId);\n }\n\n /**\n * List all active sessions\n */\n listSessions(): ExtensionSession[] {\n return Array.from(this.sessions.values());\n }\n\n /**\n * Remove a session\n */\n removeSession(sessionId: string): boolean {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return false;\n }\n\n this.browserIdToSessionId.delete(session.browserId);\n this.sessions.delete(sessionId);\n return true;\n }\n\n /**\n * Set control mode for a session\n */\n setControlMode(sessionId: string, mode: SessionControlMode): ExtensionSession | undefined {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return undefined;\n }\n\n session.controlMode = mode;\n return session;\n }\n\n /**\n * Set active spec for a session\n */\n setActiveSpec(sessionId: string, specPath: string | undefined): ExtensionSession | undefined {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return undefined;\n }\n\n session.activeSpecPath = specPath;\n if (specPath) {\n session.controlMode = 'spec';\n }\n\n return session;\n }\n\n /**\n * Cleanup stale sessions that haven't sent heartbeat\n */\n cleanupStaleSessions(maxAgeMs: number = this.defaultStaleThresholdMs): number {\n const now = Date.now();\n let removedCount = 0;\n\n for (const [sessionId, session] of this.sessions) {\n const age = now - session.lastHeartbeatAt.getTime();\n if (age > maxAgeMs) {\n this.browserIdToSessionId.delete(session.browserId);\n this.sessions.delete(sessionId);\n removedCount++;\n }\n }\n\n return removedCount;\n }\n}\n","/**\n * ExtensionSpecRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for spec execution\n * - Adapter pattern for Playwright compatibility\n * - Observer pattern for handoff signaling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Support handoff mechanism for AI control\n * - Use ExtensionPageProxy for browser operations\n *\n * AVOID:\n * - Direct Playwright usage (use proxy instead)\n * - Blocking spec execution\n * - Missing handoff handling\n */\n\nimport 'reflect-metadata/lite';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport {\n type TestBlock,\n type TestFixtures,\n type TestSuite,\n getCollectedSuite,\n initCollector,\n resetCollector,\n} from '../stubs/playwright-test.js';\nimport type { TestFilter } from '../types/index.js';\nimport type { IExtensionPageProxy } from './ExtensionPageProxy.js';\nimport type { IExtensionSessionRegistry } from './ExtensionSessionRegistry.js';\nimport type { ISpecBundlerService } from './SpecBundlerService.js';\n\n/**\n * Result of a single test execution\n */\nexport interface ExtensionTestResult {\n title: string;\n fullTitle: string;\n passed: boolean;\n error: string | null;\n duration: number;\n handoffTriggered?: boolean;\n}\n\n/**\n * Result of executing a spec file with extension mode\n */\nexport interface ExtensionSpecExecutionResult {\n specPath: string;\n totalTests: number;\n passed: number;\n failed: number;\n testResults: ExtensionTestResult[];\n success: boolean;\n duration: number;\n handoffOccurred: boolean;\n sessionId?: string;\n videoPath?: string;\n}\n\n/**\n * Options for executing a spec with extension mode\n */\nexport interface ExtensionSpecExecuteOptions {\n specPath: string;\n sessionId: string;\n testFilter?: TestFilter;\n specArgs?: Record<string, unknown>;\n onHandoff?: (sessionId: string) => Promise<void>;\n}\n\n/**\n * Extended fixtures for extension mode\n */\nexport interface ExtensionTestFixtures {\n page: IExtensionPageProxy;\n requestHandoff: (reason?: string) => Promise<void>;\n specArgs?: Record<string, unknown>;\n}\n\nexport interface IExtensionSpecRunner {\n loadSpec(specPath: string): Promise<TestSuite>;\n executeSpec(options: ExtensionSpecExecuteOptions): Promise<ExtensionSpecExecutionResult>;\n}\n\n@injectable()\nexport class ExtensionSpecRunner implements IExtensionSpecRunner {\n constructor(\n @inject(PLAYWRIGHT_TYPES.SpecBundlerService) private readonly bundler: ISpecBundlerService,\n @inject(PLAYWRIGHT_TYPES.ExtensionPageProxy) private readonly pageProxy: IExtensionPageProxy,\n @inject(PLAYWRIGHT_TYPES.ExtensionSessionRegistry) private readonly sessionRegistry: IExtensionSessionRegistry,\n ) {}\n\n /**\n * Load a spec file and collect test blocks\n */\n async loadSpec(specPath: string): Promise<TestSuite> {\n const bundleResult = await this.bundler.bundle(specPath);\n\n try {\n initCollector(specPath);\n\n // Temporarily clear Playwright's double-load guard so the bundled stub\n // can import playwright/test without triggering \"Requiring @playwright/test second time\"\n const proc = process as unknown as Record<string, unknown>;\n const savedInitiator = proc.__pw_initiator__;\n proc.__pw_initiator__ = undefined;\n\n // NOTE: Dynamic import required - bundled spec is generated at runtime in temp directory\n await import(`${bundleResult.outputPath}?t=${Date.now()}`);\n\n proc.__pw_initiator__ = savedInitiator;\n\n return getCollectedSuite();\n } catch (error) {\n resetCollector();\n throw new Error(`Failed to load spec \"${specPath}\": ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n } finally {\n await bundleResult.cleanup();\n }\n }\n\n /**\n * Execute a spec using extension mode\n */\n async executeSpec(options: ExtensionSpecExecuteOptions): Promise<ExtensionSpecExecutionResult> {\n const { specPath, sessionId, testFilter, specArgs, onHandoff } = options;\n\n const startTime = Date.now();\n const testResults: ExtensionTestResult[] = [];\n let passed = 0;\n let failed = 0;\n let handoffOccurred = false;\n\n this.sessionRegistry.setActiveSpec(sessionId, specPath);\n\n try {\n const suite = await this.loadSpec(specPath);\n const testsToRun = testFilter ? this.filterTests(suite.allTests, testFilter) : suite.allTests;\n\n const requestHandoff = async (reason?: string): Promise<void> => {\n const session = this.sessionRegistry.getSession(sessionId);\n if (!session) {\n throw new Error(`Session ${sessionId} not found`);\n }\n\n const snapshot = await this.pageProxy.getSnapshot();\n const url = this.pageProxy.url();\n\n this.sessionRegistry.requestHandoff({\n sessionId,\n reason,\n pageState: {\n url,\n snapshot,\n },\n });\n\n handoffOccurred = true;\n\n if (onHandoff) {\n await onHandoff(sessionId);\n }\n };\n\n const fixtures: ExtensionTestFixtures = {\n page: this.pageProxy,\n requestHandoff,\n specArgs,\n };\n\n for (const test of testsToRun) {\n if (test.skip) {\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: 0,\n });\n passed++;\n continue;\n }\n\n const session = this.sessionRegistry.getSession(sessionId);\n if (session?.handoffRequested) {\n break;\n }\n\n const testStartTime = Date.now();\n\n try {\n await test.fn(fixtures as unknown as TestFixtures);\n\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: Date.now() - testStartTime,\n handoffTriggered: session?.handoffRequested,\n });\n passed++;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: false,\n error: errorMessage,\n duration: Date.now() - testStartTime,\n });\n failed++;\n }\n }\n\n return {\n specPath,\n totalTests: testsToRun.length,\n passed,\n failed,\n testResults,\n success: failed === 0,\n duration: Date.now() - startTime,\n handoffOccurred,\n sessionId,\n };\n } catch (error) {\n return {\n specPath,\n totalTests: 0,\n passed: 0,\n failed: 1,\n testResults: [\n {\n title: 'Spec Loading',\n fullTitle: `Failed to load: ${specPath}`,\n passed: false,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n },\n ],\n success: false,\n duration: Date.now() - startTime,\n handoffOccurred: false,\n sessionId,\n };\n } finally {\n this.sessionRegistry.setActiveSpec(sessionId, undefined);\n }\n }\n\n /**\n * Filter tests based on criteria\n */\n private filterTests(tests: TestBlock[], filter: TestFilter): TestBlock[] {\n let filtered = [...tests];\n\n if (filter.onlyMarked) {\n const onlyTests = filtered.filter((t) => t.only);\n if (onlyTests.length > 0) {\n filtered = onlyTests;\n }\n }\n\n if (filter.testName) {\n filtered = filtered.filter((t) => t.title === filter.testName);\n }\n\n if (filter.testPattern) {\n const pattern = new RegExp(filter.testPattern, 'i');\n filtered = filtered.filter((t) => pattern.test(t.fullTitle));\n }\n\n if (filter.describeFilter) {\n const pattern = new RegExp(filter.describeFilter, 'i');\n filtered = filtered.filter((t) => pattern.test(t.fullTitle));\n }\n\n return filtered;\n }\n}\n","/**\n * Network configuration utilities\n *\n * Centralizes default host and port handling for browse-tool services and clients.\n */\n\nimport { DEFAULT_PORT_RANGE } from '@agimon-ai/foundation-port-registry';\n\n/** Default host used by browse-tool HTTP services */\nexport const DEFAULT_MCP_HOST = 'localhost';\n\n/** Default port used by browse-tool HTTP services */\nexport const DEFAULT_MCP_PORT = DEFAULT_PORT_RANGE.min;\n\n/**\n * Environment variable keys used by browse-tool for server binding.\n */\nexport const MCP_HOST_ENV = 'PLAYWRIGHT_HOST';\nexport const MCP_PORT_ENV = 'PLAYWRIGHT_PORT';\n\n/** Network configuration for browse-tool */\nexport interface PlaywrightNetworkConfig {\n /** Host name to bind the HTTP server to */\n host: string;\n /** Port to bind the HTTP server to */\n port: number;\n}\n\n/**\n * Returns the configured host for browse-tool.\n */\nexport function getPlaywrightHost(): string {\n const envHost = process.env[MCP_HOST_ENV];\n return envHost?.trim() || DEFAULT_MCP_HOST;\n}\n\n/**\n * Returns the configured port for browse-tool.\n */\nexport function getPlaywrightPort(): number {\n const envPort = process.env[MCP_PORT_ENV];\n\n if (!envPort) {\n return DEFAULT_MCP_PORT;\n }\n\n const parsed = Number.parseInt(envPort, 10);\n if (Number.isNaN(parsed) || parsed <= 0 || parsed > 65535) {\n throw new Error(`Invalid ${MCP_PORT_ENV} value: ${envPort}`);\n }\n\n return parsed;\n}\n\n/**\n * Resolve full network config from environment and optional overrides.\n */\nexport function getPlaywrightNetworkConfig(overrides: Partial<PlaywrightNetworkConfig> = {}): PlaywrightNetworkConfig {\n const resolvedHost = overrides.host ?? getPlaywrightHost();\n const resolvedPort = overrides.port ?? getPlaywrightPort();\n return {\n host: resolvedHost,\n port: resolvedPort,\n };\n}\n\n/**\n * Build a base URL for the server from host + port.\n */\nexport function buildPlaywrightBaseUrl(\n hostOrConfig: string | PlaywrightNetworkConfig | undefined = undefined,\n maybePort?: number,\n): string {\n const host = typeof hostOrConfig === 'string' ? hostOrConfig : (hostOrConfig?.host ?? getPlaywrightHost());\n const port =\n hostOrConfig && typeof hostOrConfig !== 'string' ? hostOrConfig.port : (maybePort ?? getPlaywrightPort());\n return `http://${host}:${port}`;\n}\n","/**\n * HttpBrowserClient\n *\n * HTTP client for communicating with the browse-tool HTTP server.\n * Used to delegate tool operations to the HTTP server.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n * - Synchronous blocking operations\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { buildPlaywrightBaseUrl } from '../utils/networkConfig.js';\n\n/**\n * Execute request body\n */\ninterface ExecuteRequest {\n tool: string;\n arguments: Record<string, unknown>;\n}\n\n/**\n * Execute response from HTTP server\n */\ninterface ExecuteResponse {\n success: boolean;\n result?: CallToolResult;\n error?: string;\n}\n\n/**\n * HTTP Browser Client Service\n *\n * Communicates with the browse-tool HTTP server to execute browser operations\n */\n@injectable()\nexport class HttpBrowserClient {\n private baseUrl: string;\n private readonly defaultTimeout = DEFAULT_TOOL_TIMEOUT_MS;\n\n constructor() {\n this.baseUrl = buildPlaywrightBaseUrl();\n }\n\n /**\n * Set the base URL for the HTTP server\n */\n setBaseUrl(url: string): void {\n this.baseUrl = url;\n }\n\n /**\n * Get the current base URL for the HTTP server\n */\n getBaseUrl(): string {\n return this.baseUrl;\n }\n\n /**\n * Execute a tool via the HTTP server\n *\n * @param toolName - Name of the tool to execute\n * @param args - Arguments to pass to the tool\n * @returns Tool execution result\n */\n async execute(toolName: string, args: Record<string, unknown> = {}): Promise<CallToolResult> {\n const url = `${this.baseUrl}/execute`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.defaultTimeout);\n\n try {\n const request: ExecuteRequest = {\n tool: toolName,\n arguments: args,\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n content: [{ type: 'text', text: `HTTP error ${response.status}: ${errorText}` }],\n isError: true,\n };\n }\n\n const data = (await response.json()) as ExecuteResponse;\n\n if (!data.success) {\n // Error message can be in data.error or in result.content[0].text\n const errorText = data.error || (data.result?.content?.[0] as { text?: string })?.text || 'Unknown error';\n return {\n content: [{ type: 'text', text: errorText }],\n isError: true,\n };\n }\n\n return data.result || { content: [{ type: 'text', text: 'No result returned' }] };\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === 'AbortError') {\n return {\n content: [{ type: 'text', text: `Request timed out after ${this.defaultTimeout}ms` }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n };\n }\n }\n\n /**\n * Check if the HTTP server is healthy\n *\n * @returns Whether the server is healthy\n */\n async isHealthy(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/health`, {\n headers: { 'Content-Type': 'application/json' },\n });\n\n if (!response.ok) {\n return false;\n }\n\n const data = (await response.json()) as { status?: string };\n return data.status === 'healthy';\n } catch {\n return false;\n }\n }\n}\n","/**\n * HttpServerHealthCheck\n *\n * Service for checking HTTP server health status using native fetch.\n * Pings /health endpoint to determine if server is running.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n * - Return typed results\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n * - Synchronous blocking operations\n * - Missing error handling\n */\n\nimport { injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\n/**\n * Health check result\n */\nexport interface HealthCheckResult {\n /**\n * Whether the server is healthy and responding\n */\n healthy: boolean;\n\n /**\n * Port number if server is healthy\n */\n port?: number;\n\n /**\n * Service name from health response (e.g., \"browse-tool-http\")\n */\n serviceName?: string;\n\n /**\n * Browser count if available from health response\n */\n browserCount?: number;\n\n /**\n * Error message if health check failed\n */\n error?: string;\n}\n\n/**\n * HTTP Server Health Check Service\n *\n * Checks if HTTP server is running and healthy by pinging the /health endpoint\n */\n@injectable()\nexport class HttpServerHealthCheck {\n private readonly defaultTimeout = 5000; // 5 seconds\n\n /**\n * Check if HTTP server is running and healthy\n *\n * @param port - Port number to check\n * @param timeout - Timeout in milliseconds (default: 5000)\n * @returns Health check result\n */\n async check(port: number, timeout: number = this.defaultTimeout): Promise<HealthCheckResult> {\n try {\n const url = `${buildPlaywrightBaseUrl(getPlaywrightHost(), port)}/health`;\n\n // Use AbortController for timeout\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n clearTimeout(timeoutId);\n\n // Check if response is OK (status 200-299)\n if (!response.ok) {\n return {\n healthy: false,\n error: `HTTP error ${response.status}: ${response.statusText}`,\n };\n }\n\n // Parse response body\n const data = (await response.json()) as {\n status?: string;\n service?: string;\n browsers?: { count?: number };\n };\n\n // Verify response structure\n if (data.status === 'healthy') {\n return {\n healthy: true,\n port,\n serviceName: data.service,\n browserCount: data.browsers?.count,\n };\n }\n\n return {\n healthy: false,\n error: `Unexpected health status: ${data.status || 'unknown'}`,\n };\n } catch (fetchError) {\n clearTimeout(timeoutId);\n\n // Handle different error types\n if (fetchError instanceof Error) {\n // AbortError means timeout\n if (fetchError.name === 'AbortError') {\n return {\n healthy: false,\n error: `Health check timed out after ${timeout}ms`,\n };\n }\n\n // ECONNREFUSED or other network errors\n if ('code' in fetchError && fetchError.code === 'ECONNREFUSED') {\n return {\n healthy: false,\n error: 'Connection refused - server not running',\n };\n }\n\n return {\n healthy: false,\n error: `Network error: ${fetchError.message}`,\n };\n }\n\n return {\n healthy: false,\n error: `Unknown error: ${String(fetchError)}`,\n };\n }\n } catch (error) {\n // Catch-all for unexpected errors\n return {\n healthy: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n }\n}\n","/**\n * HttpServerManager\n *\n * Lifecycle management service for singleton HTTP server coordination.\n * Handles server discovery, startup, shutdown, and health monitoring.\n *\n * Uses global (repo-agnostic) service discovery via listAllocations to\n * support git worktrees where resolveWorkspaceRoot() returns different paths.\n * All discovery is scoped by environment to prevent cross-environment interference.\n *\n * Environment variables:\n * - NODE_ENV: Determines the runtime environment (default: 'development').\n * All registry queries are scoped to this environment value.\n * - PORT_REGISTRY_PATH: Custom path for the port registry file (optional).\n * When unset, PortRegistryService uses its default location.\n * - PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS: Set to '1' on spawned child servers\n * to skip dynamic tool command loading during startup.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { ProcessRegistryService } from '@agimon-ai/foundation-process-registry';\nimport type { PortRegistryRecord, PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { getPlaywrightHost, getPlaywrightPort } from '../utils/networkConfig.js';\nimport type { HealthCheckResult, HttpServerHealthCheck } from './HttpServerHealthCheck.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Env var set on spawned child servers to skip dynamic tool loading */\nconst SKIP_DYNAMIC_TOOLS_ENV = 'PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS';\n\n/** Value that enables the SKIP_DYNAMIC_TOOLS_ENV flag */\nconst SKIP_DYNAMIC_TOOLS_ENABLED = '1';\n\n/** Files that indicate the root of a workspace/monorepo */\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\n\n/** Service name used for port registry entries */\nconst SERVICE_NAME = 'browse-tool-http';\n\n/** Service type used for port registry entries */\nconst SERVICE_TYPE = 'tool' as const;\n\n/** Service type used for process registry entries */\nconst PROCESS_SERVICE_TYPE = 'service' as const;\n\n/** CLI sub-command used to start the HTTP server */\nconst HTTP_SERVE_COMMAND = 'http-serve';\n\n/** Maximum number of spawn attempts before giving up */\nconst MAX_SPAWN_ATTEMPTS = 3;\n\n/** Milliseconds to wait for the spawned server to become healthy */\nconst SERVER_STARTUP_WAIT_MS = 10_000;\n\n/** Milliseconds to wait for spawn error events before considering spawn successful */\nconst SPAWN_ERROR_GRACE_MS = 100;\n\n/** Milliseconds to wait after SIGTERM before sending SIGKILL */\nconst KILL_GRACE_PERIOD_MS = 1_000;\n\n/** Benign errno code: process does not exist */\nconst KILL_CODE_NO_PROCESS = 'ESRCH';\n\n/** Process signal: graceful termination */\nconst SIGNAL_TERM = 'SIGTERM' as const;\n\n/** Process signal: forced kill */\nconst SIGNAL_KILL = 'SIGKILL' as const;\n\n/** CLI argument flags */\nconst CLI_FLAG_PORT = '--port';\nconst CLI_FLAG_HOST = '--host';\n\n/** Default environment when NODE_ENV is not set */\nconst DEFAULT_ENVIRONMENT = 'development';\n\n/** Node runtime command */\nconst NODE_COMMAND = 'node';\n\n/** Bun runtime command */\nconst BUN_COMMAND = 'bun';\n\n/** Bun run subcommand */\nconst BUN_RUN_SUBCOMMAND = 'run';\n\n/** Dist CLI filename */\nconst DIST_CLI_FILENAME = 'cli.mjs';\n\n/** Source CLI filename */\nconst SRC_CLI_FILENAME = 'cli.ts';\n\n/** Dist directory name */\nconst DIST_DIR = 'dist';\n\n/** Source directory name */\nconst SRC_DIR = 'src';\n\n// Error codes and messages for structured status responses\nconst ERROR_CODE_STALE_ENTRY = 'HTTP_SERVER_STALE_ENTRY';\nconst ERROR_CODE_NOT_REGISTERED = 'HTTP_SERVER_NOT_REGISTERED';\nconst ERROR_MSG_STALE = 'Server registered but not healthy';\nconst ERROR_MSG_NOT_REGISTERED = 'No HTTP server registered';\nconst ERROR_RECOVERY_STALE = 'Run ensureRunning() to start a fresh server or clean up stale entries.';\nconst ERROR_RECOVERY_NOT_REGISTERED = 'Call ensureRunning() to discover or spawn a server.';\n\n/** Sentinel string from PortRegistryService when no entry matches a release */\nconst REGISTRY_NO_MATCH_SENTINEL = 'No matching registry entry';\n\n/** Sentinel string from ProcessRegistryService when no entry matches a release */\nconst PROCESS_REGISTRY_NO_MATCH_SENTINEL = 'No matching process entry';\n\n/** Log prefix for all console output from this service */\nconst LOG_PREFIX = '[HttpServerManager]';\n\n// ============================================================================\n// Custom Error Classes\n// ============================================================================\n\n/**\n * Error thrown when no available port can be found for the HTTP server.\n */\nexport class HttpServerPortError extends Error {\n readonly code = 'HTTP_SERVER_PORT_ERROR';\n readonly recovery = 'Check if other services are occupying ports in the configured range.';\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'HttpServerPortError';\n }\n}\n\n/**\n * Error thrown when the HTTP server fails to start after all retry attempts.\n */\nexport class HttpServerStartupError extends Error {\n readonly code = 'HTTP_SERVER_STARTUP_ERROR';\n readonly recovery = 'Check server logs, ensure CLI binary exists, and verify port availability.';\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'HttpServerStartupError';\n }\n}\n\n/**\n * Error thrown when stopping the HTTP server fails.\n * Carries an array of root-cause errors from individual entry stop failures.\n */\nexport class HttpServerStopError extends Error {\n readonly code = 'HTTP_SERVER_STOP_ERROR';\n readonly recovery = 'Manually kill the process and clean up the port registry.';\n readonly causes: Error[];\n\n /**\n * @param message - Aggregated error description\n * @param causes - Array of root-cause errors from per-entry failures\n * @param options - Standard ErrorOptions for cause chaining\n */\n constructor(message: string, causes: Error[] = [], options?: ErrorOptions) {\n super(message, options);\n this.name = 'HttpServerStopError';\n this.causes = causes;\n }\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Check if a path exists using async filesystem access.\n * Only treats ENOENT as \"not found\"; rethrows unexpected filesystem errors.\n *\n * @param targetPath - Absolute path to check\n * @returns true if the path is accessible\n * @throws Rethrows non-ENOENT filesystem errors\n */\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch (error) {\n if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Resolves the workspace root directory by walking up from `startPath`\n * looking for known workspace marker files.\n *\n * @param startPath - Directory to start searching from (default: `process.cwd()`)\n * @returns The resolved workspace root path, or `process.cwd()` if no marker is found\n */\nasync function resolveWorkspaceRoot(startPath = process.cwd()): Promise<string> {\n let currentDir = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (await pathExists(path.join(currentDir, marker))) {\n return currentDir;\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return process.cwd();\n }\n currentDir = parentDir;\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * HTTP Server status returned by lifecycle operations.\n * Includes structured error fields when the server is not running.\n */\nexport interface HttpServerStatus {\n /** Whether the server is running */\n running: boolean;\n /** Port number if running */\n port?: number;\n /** Process ID if running */\n pid?: number;\n /** Browser count if available */\n browserCount?: number;\n /** Whether the server was spawned (true) or reused (false) */\n spawned?: boolean;\n /** Human-readable error message if not running */\n error?: string;\n /** Machine-readable error code for programmatic branching */\n errorCode?: string;\n /** Recovery suggestion for the error */\n errorRecovery?: string;\n /** Root cause error message when wrapping an inner error */\n errorCause?: string;\n}\n\nexport interface EnsureRunningOptions {\n /**\n * When true, only reuse or spawn the requested port.\n * Do not silently fall back to another healthy registered instance.\n */\n exactPort?: boolean;\n}\n\n/** Reason a spawn attempt failed, used for structured control flow. */\ntype SpawnFailureReason = 'health_check_failed' | 'health_check_error' | 'service_name_mismatch' | 'ownership_mismatch';\n\n/** Result of a single spawn attempt. */\ntype SpawnAttemptResult =\n | { success: true; port: number; pid: number; browserCount?: number }\n | { success: false; reason: SpawnFailureReason; port: number; error?: string };\n\n// ============================================================================\n// Service\n// ============================================================================\n\n/**\n * HTTP Server Manager Service\n *\n * Manages singleton HTTP server lifecycle with service discovery and health checks.\n * Discovery is global across repositoryPaths (to support git worktrees) but\n * scoped by environment to prevent cross-environment interference.\n */\n@injectable()\nexport class HttpServerManager {\n private repositoryPath: string | undefined;\n private readonly environment: string;\n private readonly host: string;\n\n /**\n * @param healthCheck - Injected health check service for verifying server liveness\n * @param portRegistry - Injected port registry service for service discovery and allocation\n */\n constructor(\n @inject(PLAYWRIGHT_TYPES.HttpServerHealthCheck) private readonly healthCheck: HttpServerHealthCheck,\n @inject(PLAYWRIGHT_TYPES.PortRegistryService) private readonly portRegistry: PortRegistryService,\n @inject(PLAYWRIGHT_TYPES.ProcessRegistryService)\n private readonly processRegistry: ProcessRegistryService = new ProcessRegistryService(\n process.env.PROCESS_REGISTRY_PATH,\n ),\n ) {\n this.environment = process.env.NODE_ENV || DEFAULT_ENVIRONMENT;\n this.host = getPlaywrightHost();\n }\n\n /**\n * Lazily resolve and cache the workspace root path.\n *\n * @returns The resolved workspace root\n */\n private async getRepositoryPath(): Promise<string> {\n if (!this.repositoryPath) {\n this.repositoryPath = await resolveWorkspaceRoot(process.cwd());\n }\n return this.repositoryPath;\n }\n\n /**\n * Build a structured error status from a caught error, preserving\n * error code, recovery, and cause fields from custom error classes.\n *\n * @param error - The caught error value\n * @returns An HttpServerStatus with structured error details\n */\n private buildErrorStatus(error: unknown): HttpServerStatus {\n const message = error instanceof Error ? error.message : String(error);\n const errorCode = error instanceof Error && 'code' in error ? (error as { code: string }).code : undefined;\n const errorRecovery =\n error instanceof Error && 'recovery' in error ? (error as { recovery: string }).recovery : undefined;\n const errorCause = error instanceof Error && error.cause instanceof Error ? error.cause.message : undefined;\n\n return { running: false, error: message, errorCode, errorRecovery, errorCause };\n }\n\n /**\n * List all registry entries for this service scoped by environment.\n * No repositoryPath filter so worktree-registered servers are visible.\n *\n * @returns Array of matching registry records\n */\n private async listServiceEntries(): Promise<PortRegistryRecord[]> {\n return this.portRegistry.listAllocations({\n serviceName: SERVICE_NAME,\n serviceType: SERVICE_TYPE,\n environment: this.environment,\n });\n }\n\n /**\n * Release a registry entry for a specific port and PID, using the entry's own\n * repositoryPath so the release matches the original registration.\n *\n * @param entry - The registry record to release\n */\n private async releaseEntry(entry: PortRegistryRecord): Promise<void> {\n if (entry.pid) {\n const processResponse = await this.processRegistry.releaseProcess({\n repositoryPath: entry.repositoryPath,\n serviceName: SERVICE_NAME,\n serviceType: PROCESS_SERVICE_TYPE,\n environment: entry.environment,\n pid: entry.pid,\n kill: false,\n releasePort: false,\n });\n\n if (!processResponse.success && !processResponse.error?.includes(PROCESS_REGISTRY_NO_MATCH_SENTINEL)) {\n console.error(`${LOG_PREFIX} Failed to release process entry for port ${entry.port}: ${processResponse.error}`);\n }\n }\n\n const response = await this.portRegistry.releasePort({\n repositoryPath: entry.repositoryPath,\n serviceName: SERVICE_NAME,\n serviceType: SERVICE_TYPE,\n environment: entry.environment,\n pid: entry.pid,\n });\n\n if (!response.success && !response.error?.includes(REGISTRY_NO_MATCH_SENTINEL)) {\n console.error(`${LOG_PREFIX} Failed to release entry for port ${entry.port}: ${response.error}`);\n }\n }\n\n /**\n * Discover a healthy service instance across all repository paths.\n * Searches globally (no repositoryPath filter) but scoped by environment\n * so worktree-registered servers are visible without cross-env interference.\n * Per-entry health check failures are treated as indeterminate and skipped.\n *\n * @returns The first healthy entry with browser count, or null if none found\n */\n private async discoverHealthyService(): Promise<{ entry: PortRegistryRecord; browserCount?: number } | null> {\n const entries = await this.listServiceEntries();\n\n for (const entry of entries) {\n try {\n const result = await this.healthCheck.check(entry.port);\n if (result.healthy && result.serviceName === SERVICE_NAME) {\n return { entry, browserCount: result.browserCount };\n }\n } catch (error) {\n console.error(\n `${LOG_PREFIX} Health check threw for port ${entry.port}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return null;\n }\n\n /**\n * Check whether the requested port already hosts a healthy browse-tool HTTP service.\n *\n * @param port - The exact port to verify\n * @returns Healthy service metadata for the requested port, or null if not healthy\n */\n private async discoverHealthyServiceOnPort(\n port: number,\n ): Promise<{ entry: PortRegistryRecord; browserCount?: number } | null> {\n const registration = await this.findRegistrationByPort(port);\n if (!registration) {\n return null;\n }\n\n try {\n const result = await this.healthCheck.check(port);\n if (result.healthy && result.serviceName === SERVICE_NAME) {\n return { entry: registration, browserCount: result.browserCount };\n }\n } catch (error) {\n console.error(\n `${LOG_PREFIX} Health check threw for requested port ${port}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return null;\n }\n\n /**\n * Remove stale (unhealthy) registry entries and kill dead PIDs.\n * Iterates all entries for this service scoped by environment.\n * Processes each entry independently — per-entry failures are logged and skipped.\n * Health check errors are treated as indeterminate (entry is left in place).\n */\n private async cleanupStaleEntries(): Promise<void> {\n const entries = await this.listServiceEntries();\n const errors: string[] = [];\n\n for (const entry of entries) {\n try {\n let healthResult: HealthCheckResult;\n try {\n healthResult = await this.healthCheck.check(entry.port);\n } catch (checkError) {\n // Health check threw — treat as indeterminate, skip this entry to avoid\n // removing a potentially healthy server during a transient failure.\n console.error(\n `${LOG_PREFIX} Health check threw for port ${entry.port}, skipping cleanup: ${checkError instanceof Error ? checkError.message : String(checkError)}`,\n );\n continue;\n }\n\n const isHealthy = healthResult.healthy && healthResult.serviceName === SERVICE_NAME;\n if (!isHealthy) {\n if (entry.pid) {\n await this.killProcess(entry.pid);\n }\n await this.releaseEntry(entry);\n }\n } catch (error) {\n const msg = `Failed to clean up entry on port ${entry.port}: ${error instanceof Error ? error.message : String(error)}`;\n console.error(`${LOG_PREFIX} ${msg}`);\n errors.push(msg);\n }\n }\n\n if (errors.length > 0) {\n console.error(`${LOG_PREFIX} ${errors.length} entry cleanup failure(s) during stale entry removal`);\n }\n }\n\n /**\n * Find a registry entry by port across all repository paths (scoped by environment).\n * Used after spawn to confirm the child process self-registered.\n *\n * @param port - The port number to search for\n * @returns The matching registry record, or null if not found\n */\n private async findRegistrationByPort(port: number): Promise<PortRegistryRecord | null> {\n const entries = await this.listServiceEntries();\n return entries.find((entry) => entry.port === port) ?? null;\n }\n\n /**\n * Find an available port starting from the given port number.\n *\n * @param startPort - The preferred port to start scanning from\n * @returns An available port number\n * @throws {HttpServerPortError} If no available port can be found\n */\n private async findAvailablePort(startPort: number): Promise<number> {\n const repoPath = await this.getRepositoryPath();\n const result = await this.portRegistry.findAvailablePort({\n repositoryPath: repoPath,\n serviceName: SERVICE_NAME,\n serviceType: SERVICE_TYPE,\n environment: this.environment,\n host: this.host,\n preferredPort: startPort,\n });\n\n if (!result.success || !result.port) {\n throw new HttpServerPortError(`No available port found from ${startPort}: ${result.error}`);\n }\n\n return result.port;\n }\n\n /**\n * Ensure the requested port is available without silently moving to another port.\n *\n * @param port - The exact port that must be used\n * @throws {HttpServerPortError} If a different port would be required\n */\n private async ensureExactPortAvailable(port: number): Promise<void> {\n const availablePort = await this.findAvailablePort(port);\n if (availablePort !== port) {\n throw new HttpServerPortError(`Requested port ${port} is unavailable; next available port is ${availablePort}`);\n }\n }\n\n /**\n * Build CLI command and args for spawning the HTTP server.\n *\n * @param port - The port to start the server on\n * @returns Tuple of [command, args] for spawn\n * @throws {HttpServerStartupError} If the CLI binary cannot be found\n */\n private async resolveCliCommand(port: number): Promise<[string, string[]]> {\n const distCliPath = path.resolve(__dirname, DIST_CLI_FILENAME);\n const srcCliPath = path.resolve(__dirname, '..', SRC_CLI_FILENAME);\n const portArgs = [CLI_FLAG_PORT, port.toString(), CLI_FLAG_HOST, this.host];\n\n if (await pathExists(distCliPath)) {\n return [NODE_COMMAND, [distCliPath, HTTP_SERVE_COMMAND, ...portArgs]];\n }\n\n if (await pathExists(srcCliPath)) {\n return [BUN_COMMAND, [BUN_RUN_SUBCOMMAND, srcCliPath, HTTP_SERVE_COMMAND, ...portArgs]];\n }\n\n const packageRoot = path.resolve(__dirname, '..', '..');\n const fallbackDistCli = path.join(packageRoot, DIST_DIR, DIST_CLI_FILENAME);\n const fallbackSrcCli = path.join(packageRoot, SRC_DIR, SRC_CLI_FILENAME);\n\n if (await pathExists(fallbackDistCli)) {\n return [NODE_COMMAND, [fallbackDistCli, HTTP_SERVE_COMMAND, ...portArgs]];\n }\n\n if (await pathExists(fallbackSrcCli)) {\n return [BUN_COMMAND, [BUN_RUN_SUBCOMMAND, fallbackSrcCli, HTTP_SERVE_COMMAND, ...portArgs]];\n }\n\n throw new HttpServerStartupError(\n `Cannot find CLI at ${distCliPath}, ${srcCliPath}, ${fallbackDistCli}, or ${fallbackSrcCli}`,\n );\n }\n\n /**\n * Start HTTP server as a detached background process.\n * Resolves the CLI entry point (dist or src) and spawns it with the given port.\n * Listens for spawn errors with a grace period to catch ENOENT/EACCES.\n *\n * @param port - The port to start the server on\n * @returns The PID of the spawned process\n * @throws {HttpServerStartupError} If the CLI binary cannot be found or spawn fails\n */\n private async startHttpServer(port: number): Promise<number> {\n const [command, args] = await this.resolveCliCommand(port);\n\n const child = spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n env: {\n ...process.env,\n NODE_ENV: this.environment,\n [SKIP_DYNAMIC_TOOLS_ENV]: SKIP_DYNAMIC_TOOLS_ENABLED,\n },\n });\n\n // Race: wait for either an error event or a grace timeout.\n // Keeps the listener active long enough to catch ENOENT/EACCES from the OS.\n const spawnError = await new Promise<Error | null>((resolve) => {\n const timer = setTimeout(() => {\n child.removeAllListeners('error');\n resolve(null);\n }, SPAWN_ERROR_GRACE_MS);\n\n child.once('error', (err) => {\n clearTimeout(timer);\n resolve(err);\n });\n });\n\n if (spawnError) {\n throw new HttpServerStartupError(`Failed to spawn HTTP server process: ${spawnError.message}`, {\n cause: spawnError,\n });\n }\n\n child.unref();\n\n const pid = child.pid;\n if (!pid) {\n throw new HttpServerStartupError('Failed to spawn HTTP server - no PID returned');\n }\n\n return pid;\n }\n\n /**\n * Kill a process by PID using SIGTERM, falling back to SIGKILL after a grace period.\n * Only treats ESRCH (no such process) as benign. EPERM and other errors are rethrown\n * so callers can handle permission failures explicitly.\n *\n * @param pid - The process ID to terminate\n * @throws Rethrows kill errors that are not ESRCH\n */\n private async killProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n process.kill(pid, SIGNAL_TERM);\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? (error as NodeJS.ErrnoException).code : undefined;\n if (code === KILL_CODE_NO_PROCESS) {\n console.error(`${LOG_PREFIX} Process ${pid} does not exist — skipping kill`);\n return;\n }\n throw error;\n }\n\n await new Promise((resolve) => setTimeout(resolve, KILL_GRACE_PERIOD_MS));\n\n try {\n process.kill(pid, 0);\n process.kill(pid, SIGNAL_KILL);\n } catch (error) {\n const code = error instanceof Error && 'code' in error ? (error as NodeJS.ErrnoException).code : undefined;\n if (code === KILL_CODE_NO_PROCESS) {\n console.error(`${LOG_PREFIX} Process ${pid} exited after SIGTERM`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Clean up after a failed spawn attempt: kill the process and release any\n * registry entry the child may have created for this port, but only if the\n * entry is owned by the spawned PID to avoid deleting foreign registrations.\n *\n * @param pid - The spawned process PID to kill\n * @param port - The port to look up in the registry\n */\n private async cleanupFailedSpawn(pid: number, port: number): Promise<void> {\n await this.killProcess(pid);\n const entry = await this.findRegistrationByPort(port);\n if (entry && entry.pid === pid) {\n await this.releaseEntry(entry);\n }\n }\n\n /**\n * Attempt a single spawn cycle: find port, start server, wait, health-check,\n * and verify ownership. Guarantees cleanup of the spawned process and its registry\n * entry on any failure.\n *\n * @param preferredPort - Port to start scanning from\n * @returns Structured result indicating success or typed failure reason with error context\n */\n private async attemptSpawn(preferredPort: number): Promise<SpawnAttemptResult> {\n const availablePort = await this.findAvailablePort(preferredPort);\n const pid = await this.startHttpServer(availablePort);\n\n // Wait for server startup — child self-registers with force: true\n await new Promise((resolve) => setTimeout(resolve, SERVER_STARTUP_WAIT_MS));\n\n let healthResult: HealthCheckResult;\n try {\n healthResult = await this.healthCheck.check(availablePort);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n console.error(`${LOG_PREFIX} Health check threw on port ${availablePort}: ${errorMsg}`);\n await this.cleanupFailedSpawn(pid, availablePort);\n return { success: false, reason: 'health_check_error', port: availablePort, error: errorMsg };\n }\n\n if (!healthResult.healthy) {\n await this.cleanupFailedSpawn(pid, availablePort);\n return { success: false, reason: 'health_check_failed', port: availablePort, error: healthResult.error };\n }\n\n if (healthResult.serviceName !== SERVICE_NAME) {\n await this.cleanupFailedSpawn(pid, availablePort);\n return {\n success: false,\n reason: 'service_name_mismatch',\n port: availablePort,\n error: `Expected ${SERVICE_NAME}, got ${healthResult.serviceName}`,\n };\n }\n\n // Verify the healthy instance is actually ours (child self-registered with this PID)\n const registration = await this.findRegistrationByPort(availablePort);\n if (!registration || registration.pid !== pid) {\n console.error(\n `${LOG_PREFIX} Ownership mismatch on port ${availablePort}: expected pid ${pid}, found ${registration?.pid}`,\n );\n await this.cleanupFailedSpawn(pid, availablePort);\n return { success: false, reason: 'ownership_mismatch', port: availablePort };\n }\n\n console.error(`${LOG_PREFIX} Server self-registered on port ${availablePort} (pid: ${registration.pid})`);\n\n return { success: true, port: availablePort, pid, browserCount: healthResult.browserCount };\n }\n\n /**\n * Ensure HTTP server is running (discover or start).\n *\n * Phase 1: Global discovery — find any healthy instance across all repos/worktrees.\n * Phase 2: Cleanup — remove stale entries from dead processes.\n * Phase 3: Spawn loop — start a new server, let the child self-register.\n * No parent registration to avoid double-registration conflicts.\n *\n * @param port - Preferred starting port (default: from PLAYWRIGHT_PORT env or config)\n * @returns Server status with connection details or structured error information\n */\n async ensureRunning(port = getPlaywrightPort(), options: EnsureRunningOptions = {}): Promise<HttpServerStatus> {\n try {\n if (options.exactPort) {\n const discoveredOnPort = await this.discoverHealthyServiceOnPort(port);\n if (discoveredOnPort) {\n return {\n running: true,\n port: discoveredOnPort.entry.port,\n pid: discoveredOnPort.entry.pid,\n browserCount: discoveredOnPort.browserCount,\n spawned: false,\n };\n }\n\n await this.cleanupStaleEntries();\n await this.ensureExactPortAvailable(port);\n\n const result = await this.attemptSpawn(port);\n if (result.success) {\n return {\n running: true,\n port: result.port,\n pid: result.pid,\n browserCount: result.browserCount,\n spawned: true,\n };\n }\n\n const startupError = new HttpServerStartupError(\n `Failed to start browse-tool HTTP server on requested port ${port}: ${result.reason}`,\n );\n return this.buildErrorStatus(startupError);\n }\n\n // Phase 1: Discover existing healthy server across all worktrees\n const discovered = await this.discoverHealthyService();\n if (discovered) {\n return {\n running: true,\n port: discovered.entry.port,\n pid: discovered.entry.pid,\n browserCount: discovered.browserCount,\n spawned: false,\n };\n }\n\n // Phase 2: Clean up stale entries before spawning\n await this.cleanupStaleEntries();\n\n // Phase 3: Spawn loop\n let nextPort = port;\n for (let attempt = 0; attempt < MAX_SPAWN_ATTEMPTS; attempt += 1) {\n const result = await this.attemptSpawn(nextPort);\n\n if (result.success) {\n return {\n running: true,\n port: result.port,\n pid: result.pid,\n browserCount: result.browserCount,\n spawned: true,\n };\n }\n\n console.error(`${LOG_PREFIX} Spawn attempt ${attempt + 1} failed: ${result.reason} on port ${result.port}`);\n nextPort = result.port + 1;\n }\n\n const startupError = new HttpServerStartupError(\n `Failed to start browse-tool HTTP server after ${MAX_SPAWN_ATTEMPTS} attempts`,\n );\n return this.buildErrorStatus(startupError);\n } catch (error) {\n return this.buildErrorStatus(error);\n }\n }\n\n /**\n * Stop all HTTP server instances and clean up registry entries.\n * Uses global discovery scoped by environment to find instances across all worktrees.\n * Processes each entry independently — per-entry failures are collected and reported\n * with full cause chain preserved.\n *\n * @returns true if at least one server was found and stopped\n * @throws {HttpServerStopError} If any entry fails to stop (aggregated with causes)\n */\n async stop(): Promise<boolean> {\n const entries = await this.listServiceEntries();\n\n if (entries.length === 0) {\n return false;\n }\n\n const collectedErrors: Error[] = [];\n\n for (const entry of entries) {\n try {\n if (entry.pid) {\n await this.killProcess(entry.pid);\n }\n await this.releaseEntry(entry);\n } catch (error) {\n const wrappedError =\n error instanceof Error ? error : new Error(`Failed to stop entry on port ${entry.port}: ${String(error)}`);\n console.error(`${LOG_PREFIX} ${wrappedError.message}`);\n collectedErrors.push(wrappedError);\n }\n }\n\n if (collectedErrors.length > 0) {\n const messages = collectedErrors.map((err) => err.message);\n throw new HttpServerStopError(\n `Failed to stop ${collectedErrors.length} of ${entries.length} server(s): ${messages.join('; ')}`,\n collectedErrors,\n { cause: collectedErrors[0] },\n );\n }\n\n return true;\n }\n\n /**\n * Get current HTTP server status.\n * Uses global discovery first, falls back to stale entry reporting.\n *\n * @returns Current server status with health and connection details\n */\n async getStatus(): Promise<HttpServerStatus> {\n try {\n const discovered = await this.discoverHealthyService();\n if (discovered) {\n return {\n running: true,\n port: discovered.entry.port,\n pid: discovered.entry.pid,\n browserCount: discovered.browserCount,\n };\n }\n\n // Check for stale entries to report\n const entries = await this.listServiceEntries();\n\n if (entries.length > 0) {\n const staleEntry = entries[0];\n return {\n running: false,\n port: staleEntry.port,\n pid: staleEntry.pid,\n error: ERROR_MSG_STALE,\n errorCode: ERROR_CODE_STALE_ENTRY,\n errorRecovery: ERROR_RECOVERY_STALE,\n };\n }\n\n return {\n running: false,\n error: ERROR_MSG_NOT_REGISTERED,\n errorCode: ERROR_CODE_NOT_REGISTERED,\n errorRecovery: ERROR_RECOVERY_NOT_REGISTERED,\n };\n } catch (error) {\n return this.buildErrorStatus(error);\n }\n }\n}\n","/**\n * IdleCleanupService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Dependency injection via InversifyJS\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n *\n * AVOID:\n * - Mixing concerns (keep focused on idle resource cleanup)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from './BrowserService.js';\nimport type { IPageRegistry } from './PageRegistry.js';\n\nconst PAGE_IDLE_TIMEOUT_MS = 15 * 60 * 1000;\nconst DEFAULT_BROWSER_IDLE_TIMEOUT_MS = 30 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = 60 * 1000;\nexport const BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR = 'PLAYWRIGHT_BROWSER_IDLE_TIMEOUT_MINUTES';\n\nfunction resolveBrowserIdleTimeoutMs(): number {\n const rawTimeoutMinutes = process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR];\n if (!rawTimeoutMinutes) {\n return DEFAULT_BROWSER_IDLE_TIMEOUT_MS;\n }\n\n const timeoutMinutes = Number.parseFloat(rawTimeoutMinutes);\n if (!Number.isFinite(timeoutMinutes) || timeoutMinutes <= 0) {\n return DEFAULT_BROWSER_IDLE_TIMEOUT_MS;\n }\n\n return Math.round(timeoutMinutes * 60 * 1000);\n}\n\nexport interface IIdleCleanupService {\n start(): void;\n stop(): void;\n}\n\n@injectable()\nexport class IdleCleanupService implements IIdleCleanupService {\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private readonly browserIdleTimeoutMs = resolveBrowserIdleTimeoutMs();\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.BrowserService) private browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.PageRegistry) private pageRegistry: IPageRegistry,\n ) {}\n\n /**\n * Starts the periodic idle cleanup check.\n * Runs every 60 seconds to close idle pages (>15min) and browsers (>30min).\n */\n start(): void {\n if (this.intervalId) {\n return;\n }\n this.intervalId = setInterval(() => {\n this.cleanup().catch((error) => {\n console.warn('[IdleCleanup] Error during cleanup:', error);\n });\n }, CLEANUP_INTERVAL_MS);\n\n // Allow the process to exit even if the interval is still running\n if (this.intervalId && typeof this.intervalId === 'object' && 'unref' in this.intervalId) {\n this.intervalId.unref();\n }\n\n console.warn(\n `[IdleCleanup] Started (page timeout: ${PAGE_IDLE_TIMEOUT_MS / 60000}min, browser timeout: ${this.browserIdleTimeoutMs / 60000}min)`,\n );\n }\n\n /**\n * Stops the periodic idle cleanup check.\n */\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n console.warn('[IdleCleanup] Stopped');\n }\n }\n\n /**\n * Performs a single cleanup pass: closes idle pages, then idle browsers.\n */\n private async cleanup(): Promise<void> {\n const now = Date.now();\n\n await this.cleanupIdlePages(now);\n await this.cleanupIdleBrowsers(now);\n }\n\n /**\n * Closes pages that have been idle longer than PAGE_IDLE_TIMEOUT_MS.\n */\n private async cleanupIdlePages(now: number): Promise<void> {\n const pages = this.pageRegistry.list();\n\n for (const pageEntry of pages) {\n const idleMs = now - pageEntry.lastAccessedAt.getTime();\n if (idleMs < PAGE_IDLE_TIMEOUT_MS) {\n continue;\n }\n\n console.warn(`[IdleCleanup] Closing idle page \"${pageEntry.id}\" (idle ${Math.round(idleMs / 60000)}min)`);\n\n try {\n if (pageEntry.page) {\n await pageEntry.page.close();\n } else {\n // Extension mode page - remove from registry and update browser\n const browser = this.browserService.getBrowser(pageEntry.browserId);\n if (browser) {\n browser.pageIds.delete(pageEntry.id);\n if (browser.currentPageId === pageEntry.id) {\n const remaining = Array.from(browser.pageIds);\n browser.currentPageId = remaining.length > 0 ? remaining[0] : null;\n }\n }\n this.pageRegistry.remove(pageEntry.id);\n }\n } catch (error) {\n console.warn(`[IdleCleanup] Failed to close page \"${pageEntry.id}\":`, error);\n }\n }\n }\n\n /**\n * Closes browsers that have been idle longer than their timeout.\n * Spec-origin browsers use the shorter PAGE_IDLE_TIMEOUT_MS (15min).\n * Other browsers use the configured browser idle timeout.\n */\n private async cleanupIdleBrowsers(now: number): Promise<void> {\n const browsers = this.browserService.listBrowsers();\n\n for (const browser of browsers) {\n const timeoutMs = browser.specOrigin ? PAGE_IDLE_TIMEOUT_MS : this.browserIdleTimeoutMs;\n const idleMs = now - browser.lastAccessedAt.getTime();\n if (idleMs < timeoutMs) {\n continue;\n }\n\n console.warn(`[IdleCleanup] Closing idle browser \"${browser.id}\" (idle ${Math.round(idleMs / 60000)}min)`);\n\n try {\n await this.browserService.closeBrowser(browser.id);\n } catch (error) {\n console.warn(`[IdleCleanup] Failed to close browser \"${browser.id}\":`, error);\n }\n }\n }\n}\n","/**\n * McpSessionTracker\n *\n * Tracks browsers and pages created during this MCP session.\n * When the session ends, all tracked browsers are cleaned up.\n *\n * DESIGN PATTERNS:\n * - Service pattern for session state management\n * - Observer pattern for tracking resource creation\n * - Cleanup pattern for resource release on shutdown\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on session tracking)\n * - Memory leaks from untracked resources\n */\n\nimport { injectable } from 'inversify';\nimport 'reflect-metadata/lite';\n\n/**\n * Tracked browser information\n */\nexport interface TrackedBrowser {\n browserId: string;\n mode: 'playwright' | 'extension' | 'vm' | 'stealth';\n createdAt: Date;\n pageIds: Set<string>;\n}\n\n/**\n * Tracked page information\n */\nexport interface TrackedPage {\n pageId: string;\n browserId: string;\n url?: string;\n createdAt: Date;\n}\n\n/**\n * Session state summary\n */\nexport interface SessionState {\n browsers: Array<{\n browserId: string;\n mode: string;\n createdAt: string;\n pageIds: string[];\n }>;\n pages: Array<{\n pageId: string;\n browserId: string;\n url?: string;\n createdAt: string;\n }>;\n totalBrowsers: number;\n totalPages: number;\n}\n\nexport interface IMcpSessionTracker {\n trackBrowser(browserId: string, mode: 'playwright' | 'extension' | 'vm' | 'stealth'): void;\n trackPage(pageId: string, browserId: string, url?: string): void;\n untrackBrowser(browserId: string): void;\n untrackPage(pageId: string): void;\n getBrowserIds(): string[];\n getPageIds(): string[];\n getPageIdsForBrowser(browserId: string): string[];\n getSessionState(): SessionState;\n clear(): void;\n}\n\n/**\n * MCP Session Tracker Service\n *\n * Tracks all browsers and pages created during this MCP session.\n * Provides query methods for AI to see what resources it owns.\n */\n@injectable()\nexport class McpSessionTracker implements IMcpSessionTracker {\n private browsers: Map<string, TrackedBrowser> = new Map();\n private pages: Map<string, TrackedPage> = new Map();\n\n /**\n * Track a new browser created in this session\n */\n trackBrowser(browserId: string, mode: 'playwright' | 'extension' | 'vm' | 'stealth'): void {\n if (this.browsers.has(browserId)) {\n return; // Already tracked\n }\n\n this.browsers.set(browserId, {\n browserId,\n mode,\n createdAt: new Date(),\n pageIds: new Set(),\n });\n }\n\n /**\n * Track a new page created in this session\n */\n trackPage(pageId: string, browserId: string, url?: string): void {\n if (this.pages.has(pageId)) {\n return; // Already tracked\n }\n\n this.pages.set(pageId, {\n pageId,\n browserId,\n url,\n createdAt: new Date(),\n });\n\n // Also track in browser's page set\n const browser = this.browsers.get(browserId);\n if (browser) {\n browser.pageIds.add(pageId);\n }\n }\n\n /**\n * Stop tracking a browser (e.g., when closed)\n */\n untrackBrowser(browserId: string): void {\n const browser = this.browsers.get(browserId);\n if (browser) {\n // Also remove all pages for this browser\n for (const pageId of browser.pageIds) {\n this.pages.delete(pageId);\n }\n this.browsers.delete(browserId);\n }\n }\n\n /**\n * Stop tracking a page (e.g., when closed)\n */\n untrackPage(pageId: string): void {\n const page = this.pages.get(pageId);\n if (page) {\n // Remove from browser's page set\n const browser = this.browsers.get(page.browserId);\n if (browser) {\n browser.pageIds.delete(pageId);\n }\n this.pages.delete(pageId);\n }\n }\n\n /**\n * Get all browser IDs tracked in this session\n */\n getBrowserIds(): string[] {\n return Array.from(this.browsers.keys());\n }\n\n /**\n * Get all page IDs tracked in this session\n */\n getPageIds(): string[] {\n return Array.from(this.pages.keys());\n }\n\n /**\n * Get page IDs for a specific browser\n */\n getPageIdsForBrowser(browserId: string): string[] {\n const browser = this.browsers.get(browserId);\n return browser ? Array.from(browser.pageIds) : [];\n }\n\n /**\n * Get full session state for AI to query\n */\n getSessionState(): SessionState {\n const browsers = Array.from(this.browsers.values()).map((b) => ({\n browserId: b.browserId,\n mode: b.mode,\n createdAt: b.createdAt.toISOString(),\n pageIds: Array.from(b.pageIds),\n }));\n\n const pages = Array.from(this.pages.values()).map((p) => ({\n pageId: p.pageId,\n browserId: p.browserId,\n url: p.url,\n createdAt: p.createdAt.toISOString(),\n }));\n\n return {\n browsers,\n pages,\n totalBrowsers: this.browsers.size,\n totalPages: this.pages.size,\n };\n }\n\n /**\n * Clear all tracked resources (for cleanup)\n */\n clear(): void {\n this.browsers.clear();\n this.pages.clear();\n }\n}\n","/**\n * PageMonitorService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Event-driven pattern for capturing page events\n * - Registry pattern for storing captured data\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n *\n * AVOID:\n * - Mixing concerns (keep focused on monitoring domain)\n * - Memory leaks (clean up listeners on page close)\n */\n\nimport 'reflect-metadata/lite';\nimport { injectable } from 'inversify';\nimport type { ConsoleMessage, Page, Request, Response } from 'playwright';\n\n/**\n * Captured network request data\n */\nexport interface NetworkRequestEntry {\n id: string;\n url: string;\n method: string;\n resourceType: string;\n headers: Record<string, string>;\n postData?: string;\n timestamp: Date;\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n timing?: number;\n };\n}\n\n/**\n * Captured console message data\n */\nexport interface ConsoleMessageEntry {\n id: string;\n type: string;\n text: string;\n location?: {\n url: string;\n lineNumber: number;\n columnNumber: number;\n };\n timestamp: Date;\n}\n\n/**\n * Page monitoring state\n */\ninterface PageMonitorState {\n networkRequests: Map<string, NetworkRequestEntry>;\n consoleMessages: ConsoleMessageEntry[];\n requestCounter: number;\n messageCounter: number;\n listeners: {\n request?: (request: Request) => void;\n response?: (response: Response) => void;\n console?: (message: ConsoleMessage) => void;\n };\n}\n\nexport interface IPageMonitorService {\n startMonitoring(pageId: string, page: Page): void;\n stopMonitoring(pageId: string): void;\n getNetworkRequests(pageId: string): NetworkRequestEntry[];\n getNetworkRequest(pageId: string, requestId: string): NetworkRequestEntry | undefined;\n getConsoleMessages(pageId: string, type?: string): ConsoleMessageEntry[];\n clearData(pageId: string): void;\n}\n\n@injectable()\nexport class PageMonitorService implements IPageMonitorService {\n private pageStates: Map<string, PageMonitorState> = new Map();\n private pageInstances: Map<string, Page> = new Map();\n\n /**\n * Starts monitoring network requests and console messages for a page.\n */\n startMonitoring(pageId: string, page: Page): void {\n // Stop existing monitoring if any\n this.stopMonitoring(pageId);\n\n const state: PageMonitorState = {\n networkRequests: new Map(),\n consoleMessages: [],\n requestCounter: 0,\n messageCounter: 0,\n listeners: {},\n };\n\n // Network request listener\n state.listeners.request = (request: Request) => {\n const id = `req-${++state.requestCounter}`;\n const entry: NetworkRequestEntry = {\n id,\n url: request.url(),\n method: request.method(),\n resourceType: request.resourceType(),\n headers: request.headers(),\n postData: request.postData() ?? undefined,\n timestamp: new Date(),\n };\n state.networkRequests.set(id, entry);\n };\n\n // Network response listener\n state.listeners.response = (response: Response) => {\n const request = response.request();\n // Find the matching request entry\n for (const [, entry] of state.networkRequests) {\n if (entry.url === request.url() && !entry.response) {\n entry.response = {\n status: response.status(),\n statusText: response.statusText(),\n headers: response.headers(),\n timing: response.request().timing().responseEnd,\n };\n break;\n }\n }\n };\n\n // Console message listener\n state.listeners.console = (message: ConsoleMessage) => {\n const id = `msg-${++state.messageCounter}`;\n const location = message.location();\n const entry: ConsoleMessageEntry = {\n id,\n type: message.type(),\n text: message.text(),\n location: location.url\n ? {\n url: location.url,\n lineNumber: location.lineNumber,\n columnNumber: location.columnNumber,\n }\n : undefined,\n timestamp: new Date(),\n };\n state.consoleMessages.push(entry);\n };\n\n // Attach listeners\n page.on('request', state.listeners.request);\n page.on('response', state.listeners.response);\n page.on('console', state.listeners.console);\n\n this.pageStates.set(pageId, state);\n this.pageInstances.set(pageId, page);\n\n // Clean up on page close\n page.once('close', () => {\n this.stopMonitoring(pageId);\n });\n }\n\n /**\n * Stops monitoring and removes listeners for a page.\n */\n stopMonitoring(pageId: string): void {\n const state = this.pageStates.get(pageId);\n const page = this.pageInstances.get(pageId);\n\n if (state && page) {\n if (state.listeners.request) {\n page.off('request', state.listeners.request);\n }\n if (state.listeners.response) {\n page.off('response', state.listeners.response);\n }\n if (state.listeners.console) {\n page.off('console', state.listeners.console);\n }\n }\n\n this.pageStates.delete(pageId);\n this.pageInstances.delete(pageId);\n }\n\n /**\n * Gets all network requests for a page.\n */\n getNetworkRequests(pageId: string): NetworkRequestEntry[] {\n const state = this.pageStates.get(pageId);\n if (!state) {\n return [];\n }\n return Array.from(state.networkRequests.values());\n }\n\n /**\n * Gets a specific network request by ID.\n */\n getNetworkRequest(pageId: string, requestId: string): NetworkRequestEntry | undefined {\n const state = this.pageStates.get(pageId);\n if (!state) {\n return undefined;\n }\n return state.networkRequests.get(requestId);\n }\n\n /**\n * Gets console messages for a page, optionally filtered by type.\n */\n getConsoleMessages(pageId: string, type?: string): ConsoleMessageEntry[] {\n const state = this.pageStates.get(pageId);\n if (!state) {\n return [];\n }\n if (type) {\n return state.consoleMessages.filter((msg) => msg.type === type);\n }\n return [...state.consoleMessages];\n }\n\n /**\n * Clears all captured data for a page.\n */\n clearData(pageId: string): void {\n const state = this.pageStates.get(pageId);\n if (state) {\n state.networkRequests.clear();\n state.consoleMessages = [];\n state.requestCounter = 0;\n state.messageCounter = 0;\n }\n }\n}\n","import 'reflect-metadata/lite';\nimport { inject, injectable, optional } from 'inversify';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserMode } from './BrowserService.js';\nimport type { WebSocketHub } from './WebSocketHub.js';\n\n/**\n * Represents a registered page with its metadata.\n */\nexport interface PageEntry {\n /** Unique page identifier */\n id: string;\n /** Execution mode for this page */\n mode: BrowserMode;\n /** Playwright Page instance (undefined for extension mode) */\n page?: Page;\n /** Browser instance this page belongs to (undefined for extension mode) */\n browser?: Browser;\n /** Browser context this page belongs to (undefined for extension mode) */\n context?: BrowserContext;\n /** Browser instance ID */\n browserId: string;\n /** Current URL */\n url: string;\n /** Page title */\n title: string;\n /** Profile name used for this page's browser */\n profileName?: string;\n /** Chrome extension tab ID (for extension mode) */\n extensionTabId?: number;\n /** Creation timestamp */\n createdAt: Date;\n /** Last activity timestamp (updated on tool execution) */\n lastAccessedAt: Date;\n /** Callback to notify when page is closed */\n onClose?: (pageId: string) => void;\n}\n\nexport interface RegisterPageOptions {\n page: Page;\n /** Browser instance (optional for persistent context mode) */\n browser?: Browser;\n context: BrowserContext;\n browserId: string;\n profileName?: string;\n /** Execution mode - defaults to 'playwright' */\n mode?: BrowserMode;\n onClose?: (pageId: string) => void;\n}\n\nexport interface IPageRegistry {\n register(options: RegisterPageOptions): Promise<string>;\n /** Register an extension mode page (virtual page without Playwright instance) */\n registerExtensionPage(browserId: string, tabId?: number, url?: string, notifyExtension?: boolean): string;\n get(id: string): PageEntry | undefined;\n remove(id: string): void;\n list(): PageEntry[];\n findByBrowser(browserId: string): PageEntry[];\n findByProfile(profileName: string): PageEntry[];\n updateMetadata(id: string): Promise<void>;\n /** Update lastAccessedAt timestamp for a page */\n touchPage(id: string): void;\n clear(): void;\n}\n\n@injectable()\nexport class PageRegistry implements IPageRegistry {\n private pages: Map<string, PageEntry> = new Map();\n private pageIdCounter = 0;\n\n constructor(@inject(PLAYWRIGHT_TYPES.WebSocketHub) @optional() private webSocketHub?: WebSocketHub) {}\n\n /**\n * Registers a new page in the registry.\n */\n async register(options: RegisterPageOptions): Promise<string> {\n const { page, browser, context, browserId, profileName, mode, onClose } = options;\n const id = `page-${++this.pageIdCounter}`;\n\n const entry: PageEntry = {\n id,\n mode: mode ?? 'playwright',\n page,\n browser,\n context,\n browserId,\n url: page.url(),\n title: await page.title(),\n profileName,\n createdAt: new Date(),\n lastAccessedAt: new Date(),\n onClose,\n };\n\n this.pages.set(id, entry);\n\n // Auto-remove on page close\n page.once('close', () => {\n try {\n onClose?.(id);\n } catch {\n // onClose callback must not prevent registry cleanup\n }\n this.remove(id);\n });\n\n return id;\n }\n\n /**\n * Registers a virtual page for extension mode.\n * Creates a page entry without Playwright instances and notifies connected extensions.\n */\n registerExtensionPage(browserId: string, tabId?: number, url?: string, notifyExtension = true): string {\n const id = `page-${++this.pageIdCounter}`;\n\n const entry: PageEntry = {\n id,\n mode: 'extension',\n page: undefined,\n browser: undefined,\n context: undefined,\n browserId,\n url: url ?? 'about:blank',\n title: 'Extension Tab',\n extensionTabId: tabId,\n createdAt: new Date(),\n lastAccessedAt: new Date(),\n };\n\n this.pages.set(id, entry);\n\n // Notify connected extensions about the new page\n if (notifyExtension) {\n this.webSocketHub?.broadcastPageCreated(browserId, id, url);\n }\n\n return id;\n }\n\n /**\n * Gets a page by ID.\n */\n get(id: string): PageEntry | undefined {\n return this.pages.get(id);\n }\n\n /**\n * Removes a page from the registry and notifies connected extensions.\n */\n remove(id: string): void {\n const entry = this.pages.get(id);\n if (entry) {\n this.pages.delete(id);\n // Notify connected extensions about page removal for extension mode pages\n if (entry.mode === 'extension') {\n this.webSocketHub?.broadcastPageRemoved(entry.browserId, id);\n }\n }\n }\n\n /**\n * Lists all registered pages.\n */\n list(): PageEntry[] {\n return Array.from(this.pages.values());\n }\n\n /**\n * Finds all pages belonging to a browser instance.\n */\n findByBrowser(browserId: string): PageEntry[] {\n return this.list().filter((entry) => entry.browserId === browserId);\n }\n\n /**\n * Finds all pages using a specific profile.\n */\n findByProfile(profileName: string): PageEntry[] {\n return this.list().filter((entry) => entry.profileName === profileName);\n }\n\n /**\n * Updates lastAccessedAt timestamp for a page to track idle state.\n */\n touchPage(id: string): void {\n const entry = this.pages.get(id);\n if (entry) {\n entry.lastAccessedAt = new Date();\n }\n }\n\n /**\n * Updates a page's URL and title metadata.\n * Only works for playwright mode pages.\n */\n async updateMetadata(id: string): Promise<void> {\n const entry = this.get(id);\n if (entry && entry.page) {\n entry.url = entry.page.url();\n entry.title = await entry.page.title();\n }\n }\n\n /**\n * Clears all pages from the registry.\n */\n clear(): void {\n this.pages.clear();\n this.pageIdCounter = 0;\n }\n}\n","/**\n * PauseController\n *\n * DESIGN PATTERNS:\n * - Service pattern for automation pause/resume control\n * - Dependency injection via InversifyJS\n * - Single responsibility: pause state management\n *\n * CODING STANDARDS:\n * - Promise-based waiting mechanism\n * - Each automation session has independent pause state\n * - Thread-safe pause/resume operations\n *\n * AVOID:\n * - Global pause state (must be session-scoped)\n * - Mixing pause logic with script execution\n */\n\nimport 'reflect-metadata/lite';\nimport { injectable } from 'inversify';\n\n/**\n * Represents the pause state of an automation session.\n */\nexport interface PauseState {\n /** Whether the session is currently paused */\n isPaused: boolean;\n /** Current step name where paused (if paused) */\n stepName: string | null;\n /** Reason for pausing (if paused) */\n reason: string | null;\n /** Timestamp when paused */\n pausedAt: Date | null;\n /** Resolve function to continue execution */\n resolver: (() => void) | null;\n}\n\n/**\n * Result returned when checking pause status.\n */\nexport interface PauseStatus {\n sessionId: string;\n isPaused: boolean;\n stepName: string | null;\n reason: string | null;\n pausedAt: Date | null;\n}\n\nexport interface IPauseController {\n /**\n * Pauses an automation session at a named step.\n * Returns a promise that resolves when resume() is called.\n */\n pause(sessionId: string, stepName: string, reason?: string): Promise<void>;\n\n /**\n * Resumes a paused automation session.\n * Resolves the waiting promise to continue execution.\n */\n resume(sessionId: string): boolean;\n\n /**\n * Returns a promise that resolves when the session is resumed.\n * If not paused, resolves immediately.\n */\n waitForResume(sessionId: string): Promise<void>;\n\n /**\n * Gets the current pause status of a session.\n */\n getStatus(sessionId: string): PauseStatus;\n\n /**\n * Checks if a session is currently paused.\n */\n isPaused(sessionId: string): boolean;\n\n /**\n * Clears all pause state for a session (cleanup on session end).\n */\n clear(sessionId: string): void;\n}\n\n@injectable()\nexport class PauseController implements IPauseController {\n private pauseStates: Map<string, PauseState> = new Map();\n\n /**\n * Gets or creates the pause state for a session.\n */\n private getOrCreateState(sessionId: string): PauseState {\n let state = this.pauseStates.get(sessionId);\n if (!state) {\n state = {\n isPaused: false,\n stepName: null,\n reason: null,\n pausedAt: null,\n resolver: null,\n };\n this.pauseStates.set(sessionId, state);\n }\n return state;\n }\n\n /**\n * Pauses an automation session at a named step.\n * Returns a promise that resolves when resume() is called.\n */\n pause(sessionId: string, stepName: string, reason?: string): Promise<void> {\n const state = this.getOrCreateState(sessionId);\n\n if (state.isPaused) {\n throw new Error(`Session \"${sessionId}\" is already paused at step \"${state.stepName}\"`);\n }\n\n state.isPaused = true;\n state.stepName = stepName;\n state.reason = reason ?? null;\n state.pausedAt = new Date();\n\n return new Promise<void>((resolve) => {\n state.resolver = resolve;\n });\n }\n\n /**\n * Resumes a paused automation session.\n * Returns true if the session was paused and is now resumed.\n */\n resume(sessionId: string): boolean {\n const state = this.pauseStates.get(sessionId);\n\n if (!state || !state.isPaused) {\n return false;\n }\n\n const resolver = state.resolver;\n\n state.isPaused = false;\n state.stepName = null;\n state.reason = null;\n state.pausedAt = null;\n state.resolver = null;\n\n if (resolver) {\n resolver();\n }\n\n return true;\n }\n\n /**\n * Returns a promise that resolves when the session is resumed.\n * If not paused, resolves immediately.\n */\n waitForResume(sessionId: string): Promise<void> {\n const state = this.pauseStates.get(sessionId);\n\n if (!state || !state.isPaused || !state.resolver) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve) => {\n const originalResolver = state.resolver;\n state.resolver = () => {\n if (originalResolver) {\n originalResolver();\n }\n resolve();\n };\n });\n }\n\n /**\n * Gets the current pause status of a session.\n */\n getStatus(sessionId: string): PauseStatus {\n const state = this.pauseStates.get(sessionId);\n\n return {\n sessionId,\n isPaused: state?.isPaused ?? false,\n stepName: state?.stepName ?? null,\n reason: state?.reason ?? null,\n pausedAt: state?.pausedAt ?? null,\n };\n }\n\n /**\n * Checks if a session is currently paused.\n */\n isPaused(sessionId: string): boolean {\n const state = this.pauseStates.get(sessionId);\n return state?.isPaused ?? false;\n }\n\n /**\n * Clears all pause state for a session.\n */\n clear(sessionId: string): void {\n const state = this.pauseStates.get(sessionId);\n\n if (state?.resolver) {\n state.resolver();\n }\n\n this.pauseStates.delete(sessionId);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { injectable } from 'inversify';\n\nexport interface BrowserProfile {\n name: string;\n browserType: 'chromium' | 'firefox' | 'webkit';\n viewport?: { width: number; height: number };\n userAgent?: string;\n locale?: string;\n timezone?: string;\n geolocation?: { latitude: number; longitude: number };\n permissions?: string[];\n colorScheme?: 'light' | 'dark' | 'no-preference';\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IProfileService {\n getProfilesDir(): string;\n list(): Promise<BrowserProfile[]>;\n get(name: string): Promise<BrowserProfile | null>;\n create(profile: Omit<BrowserProfile, 'createdAt' | 'updatedAt'>): Promise<BrowserProfile>;\n update(\n name: string,\n updates: Partial<Omit<BrowserProfile, 'name' | 'createdAt' | 'updatedAt'>>,\n ): Promise<BrowserProfile>;\n delete(name: string): Promise<void>;\n saveStorageState(name: string, state: Record<string, unknown>): Promise<void>;\n loadStorageState(name: string): Promise<Record<string, unknown> | null>;\n}\n\n@injectable()\nexport class ProfileService implements IProfileService {\n private readonly profilesDir: string;\n\n constructor() {\n // Allow custom profiles directory via environment variable\n this.profilesDir = process.env.PLAYWRIGHT_PROFILES_DIR || join(homedir(), '.browse-tool', 'profiles');\n }\n\n getProfilesDir(): string {\n return this.profilesDir;\n }\n\n private getProfileDir(name: string): string {\n return join(this.getProfilesDir(), name);\n }\n\n private getProfilePath(name: string): string {\n return join(this.getProfileDir(name), 'profile.json');\n }\n\n private getStorageStatePath(name: string): string {\n return join(this.getProfileDir(name), 'storage-state.json');\n }\n\n private async ensureProfilesDir(): Promise<void> {\n const profilesDir = this.getProfilesDir();\n if (!existsSync(profilesDir)) {\n await mkdir(profilesDir, { recursive: true });\n }\n }\n\n async list(): Promise<BrowserProfile[]> {\n await this.ensureProfilesDir();\n\n const entries = await readdir(this.getProfilesDir(), { withFileTypes: true });\n const profiles: BrowserProfile[] = [];\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const profile = await this.get(entry.name);\n if (profile) {\n profiles.push(profile);\n }\n }\n }\n\n return profiles;\n }\n\n async get(name: string): Promise<BrowserProfile | null> {\n const profilePath = this.getProfilePath(name);\n\n if (!existsSync(profilePath)) {\n return null;\n }\n\n const content = await readFile(profilePath, 'utf-8');\n return JSON.parse(content) as BrowserProfile;\n }\n\n async create(profile: Omit<BrowserProfile, 'createdAt' | 'updatedAt'>): Promise<BrowserProfile> {\n const existing = await this.get(profile.name);\n if (existing) {\n throw new Error(`Profile \"${profile.name}\" already exists`);\n }\n\n const profileDir = this.getProfileDir(profile.name);\n await mkdir(profileDir, { recursive: true });\n\n const now = new Date().toISOString();\n const fullProfile: BrowserProfile = {\n ...profile,\n createdAt: now,\n updatedAt: now,\n };\n\n await writeFile(this.getProfilePath(profile.name), JSON.stringify(fullProfile, null, 2));\n return fullProfile;\n }\n\n async update(\n name: string,\n updates: Partial<Omit<BrowserProfile, 'name' | 'createdAt' | 'updatedAt'>>,\n ): Promise<BrowserProfile> {\n const existing = await this.get(name);\n if (!existing) {\n throw new Error(`Profile \"${name}\" not found`);\n }\n\n const updatedProfile: BrowserProfile = {\n ...existing,\n ...updates,\n name: existing.name,\n createdAt: existing.createdAt,\n updatedAt: new Date().toISOString(),\n };\n\n await writeFile(this.getProfilePath(name), JSON.stringify(updatedProfile, null, 2));\n return updatedProfile;\n }\n\n async delete(name: string): Promise<void> {\n const existing = await this.get(name);\n if (!existing) {\n throw new Error(`Profile \"${name}\" not found`);\n }\n\n await rm(this.getProfileDir(name), { recursive: true });\n }\n\n async saveStorageState(name: string, state: Record<string, unknown>): Promise<void> {\n const existing = await this.get(name);\n if (!existing) {\n throw new Error(`Profile \"${name}\" not found`);\n }\n\n await writeFile(this.getStorageStatePath(name), JSON.stringify(state, null, 2));\n }\n\n async loadStorageState(name: string): Promise<Record<string, unknown> | null> {\n const storagePath = this.getStorageStatePath(name);\n\n if (!existsSync(storagePath)) {\n return null;\n }\n\n const content = await readFile(storagePath, 'utf-8');\n return JSON.parse(content) as Record<string, unknown>;\n }\n}\n","/**\n * RemoteToolExecutor\n *\n * Wraps HttpBrowserClient for tool execution delegation.\n * Provides a unified interface for executing tools via HTTP.\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection with InversifyJS\n * - Facade pattern for simplified HTTP client access\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n * - Synchronous blocking operations\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { buildPlaywrightBaseUrl } from '../utils/networkConfig.js';\nimport type { HttpBrowserClient } from './HttpBrowserClient.js';\n\n/**\n * Response from the /tools endpoint\n */\ninterface ToolsResponse {\n tools: ToolDefinition[];\n error?: string;\n}\n\n/**\n * Remote Tool Executor Service\n *\n * Facade service for executing tools via HTTP server\n */\n@injectable()\nexport class RemoteToolExecutor {\n constructor(@inject(PLAYWRIGHT_TYPES.HttpBrowserClient) private httpClient: HttpBrowserClient) {}\n\n /**\n * Execute a tool via the HTTP server\n *\n * @param toolName - Name of the tool to execute\n * @param args - Arguments to pass to the tool\n * @returns Tool execution result\n */\n async execute(toolName: string, args: Record<string, unknown> = {}): Promise<CallToolResult> {\n return this.httpClient.execute(toolName, args);\n }\n\n /**\n * Check if the HTTP server is available\n *\n * @returns Whether the server is healthy\n */\n async isServerAvailable(): Promise<boolean> {\n return this.httpClient.isHealthy();\n }\n\n /**\n * Configure the HTTP server endpoint\n *\n * @param port - Port number of the HTTP server\n */\n setServerPort(port: number): void {\n this.httpClient.setBaseUrl(buildPlaywrightBaseUrl(undefined, port));\n }\n\n /**\n * List all available tools from the HTTP server\n *\n * @returns Array of tool definitions\n */\n async listTools(): Promise<ToolDefinition[]> {\n const baseUrl = this.httpClient.getBaseUrl();\n const response = await fetch(`${baseUrl}/tools`);\n\n if (!response.ok) {\n throw new Error(`Failed to list tools: ${response.statusText}`);\n }\n\n const data = (await response.json()) as ToolsResponse;\n\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.tools;\n }\n}\n","/**\n * SetupRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for executing setup files before specs\n * - Dependency injection via InversifyJS\n * - Single responsibility: setup file execution\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Handle dynamic imports safely\n *\n * AVOID:\n * - Mixing concerns (keep focused on setup execution)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { inject, injectable } from 'inversify';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ISpecBundlerService } from './SpecBundlerService.js';\n\n/** Context passed to setup functions */\nexport interface SetupContext {\n /** Playwright Browser instance (optional for extension mode) */\n browser?: Browser;\n /** Playwright BrowserContext instance */\n context: BrowserContext;\n /** Playwright Page instance */\n page: Page;\n}\n\n/** Result of running a setup file */\nexport interface SetupResult {\n /** State returned by the setup function to pass to specs */\n state: Record<string, unknown>;\n /** Whether the setup was successful */\n success: boolean;\n /** Error message if setup failed */\n error?: string;\n}\n\n/** Setup function signature */\nexport type SetupFunction = (context: SetupContext) => Promise<Record<string, unknown>> | Record<string, unknown>;\n\nexport interface ISetupRunner {\n /**\n * Executes a setup file and returns the resulting state.\n * @param setupPath - Path to the setup file\n * @param exportName - Name of the export to call (default: 'default')\n * @param context - Browser context for setup execution\n */\n runSetup(setupPath: string, exportName: string, context: SetupContext): Promise<SetupResult>;\n}\n\n@injectable()\nexport class SetupRunner implements ISetupRunner {\n constructor(\n @inject(PLAYWRIGHT_TYPES.SpecBundlerService)\n private readonly bundler: ISpecBundlerService,\n ) {}\n\n /**\n * Executes a setup file and returns the resulting state.\n * Bundles the setup file, imports it, and calls the specified export.\n */\n async runSetup(setupPath: string, exportName: string, context: SetupContext): Promise<SetupResult> {\n let bundleResult: { outputPath: string; cleanup: () => Promise<void> } | null = null;\n\n try {\n bundleResult = await this.bundler.bundle(setupPath);\n\n // Import the bundled setup file with cache-busting\n const setupModule = await import(`${bundleResult.outputPath}?t=${Date.now()}`);\n\n // Get the setup function from the module\n const setupFn = this.getSetupFunction(setupModule, exportName, setupPath);\n\n // Execute the setup function with context\n const result = await setupFn(context);\n\n // Ensure result is an object\n const state = this.validateSetupResult(result, setupPath);\n\n return { state, success: true };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n state: {},\n success: false,\n error: `Setup failed for \"${setupPath}\": ${errorMessage}`,\n };\n } finally {\n if (bundleResult) {\n await bundleResult.cleanup();\n }\n }\n }\n\n /**\n * Gets the setup function from the module by export name.\n * Supports both named exports and default export.\n */\n private getSetupFunction(module: Record<string, unknown>, exportName: string, setupPath: string): SetupFunction {\n // Try the specified export name first\n if (exportName in module && typeof module[exportName] === 'function') {\n return module[exportName] as SetupFunction;\n }\n\n // If looking for default and it exists\n if (exportName === 'default' && 'default' in module) {\n const defaultExport = module.default;\n if (typeof defaultExport === 'function') {\n return defaultExport as SetupFunction;\n }\n throw new Error(`Default export in \"${setupPath}\" is not a function`);\n }\n\n // List available exports for better error message\n const availableExports = Object.keys(module).filter((key) => typeof module[key] === 'function');\n\n if (availableExports.length === 0) {\n throw new Error(`No function exports found in \"${setupPath}\"`);\n }\n\n throw new Error(\n `Export \"${exportName}\" not found in \"${setupPath}\". Available functions: ${availableExports.join(', ')}`,\n );\n }\n\n /**\n * Validates that the setup result is a valid object.\n */\n private validateSetupResult(result: unknown, setupPath: string): Record<string, unknown> {\n if (result === null || result === undefined) {\n return {};\n }\n\n if (typeof result !== 'object' || Array.isArray(result)) {\n throw new Error(`Setup function in \"${setupPath}\" must return an object or undefined, got ${typeof result}`);\n }\n\n return result as Record<string, unknown>;\n }\n}\n","/**\n * Bundles Playwright spec files with `@playwright/test` aliased to a stub module.\n *\n * Uses the esbuild JS API to produce a single ESM bundle. esbuild preserves\n * `import.meta.*` and `process.env.*` references by default (no inlining),\n * and does not transform `createRequire` calls into static imports.\n *\n * A node_modules symlink is created in the output directory so external\n * packages (playwright, playwright-core) can be resolved at runtime.\n *\n * @example\n * ```ts\n * const bundler = container.get<ISpecBundlerService>(TYPES.SpecBundlerService);\n * const { outputPath, cleanup } = await bundler.bundle('/path/to/my-spec.ts');\n * try {\n * // use outputPath ...\n * } finally {\n * await cleanup();\n * }\n * ```\n */\n\nimport 'reflect-metadata/lite';\nimport { existsSync } from 'node:fs';\nimport { mkdir, symlink, unlink } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { build as esbuildBuild } from 'esbuild';\nimport { injectable } from 'inversify';\n\nconst LOG_PREFIX = '[SpecBundler]';\nconst FILE_URL_PREFIX = 'file://';\nconst PLAYWRIGHT_TEST_IMPORT_SOURCE = '^@playwright\\\\/test$';\nconst PLAYWRIGHT_TEST_IMPORT_FILTER = new RegExp(PLAYWRIGHT_TEST_IMPORT_SOURCE);\n\n/** tsdown builds the stub entry into `dist/stubs/playwright-test.mjs`. */\nconst STUB_DIR = 'stubs';\nconst STUB_FILENAME = 'playwright-test.mjs';\n\nconst STUB_DEFINE_KEY = '__PLAYWRIGHT_MCP_STUB_PATH__';\n\nconst BUNDLE = {\n OUTDIR_NAME: 'browse-tool-bundles',\n PLUGIN_NAME: 'playwright-test-alias',\n EXTERNALS: ['playwright', 'playwright/test', 'playwright-core'],\n} as const;\n\nconst ERROR_CODES = {\n BUILD_FAILED: 'SPEC_BUNDLER_BUILD_FAILED',\n NO_OUTPUT: 'SPEC_BUNDLER_NO_OUTPUT',\n IO_FAILED: 'SPEC_BUNDLER_IO_FAILED',\n} as const;\n\nconst ERROR_MESSAGES = {\n UNEXPECTED_BUILD: 'Unexpected build exception',\n IO_MKDIR: 'Failed to create output directory',\n CLEANUP_FAILED: 'Failed to clean up temporary file',\n} as const;\n\n/** Raised when esbuild reports compilation errors. */\nexport class SpecBundlerBuildError extends Error {\n readonly code = ERROR_CODES.BUILD_FAILED;\n readonly recovery = 'Check the spec file for syntax errors and ensure all imports are resolvable.';\n\n constructor(\n public readonly specPath: string,\n public readonly buildErrors: string,\n options?: ErrorOptions,\n ) {\n super(`Failed to bundle spec \"${basename(specPath)}\": ${buildErrors}`, options);\n this.name = 'SpecBundlerBuildError';\n }\n}\n\n/** Raised when bundling completes successfully but produces no output artifact. */\nexport class SpecBundlerNoOutputError extends Error {\n readonly code = ERROR_CODES.NO_OUTPUT;\n readonly recovery = 'Verify the spec file exists and contains valid TypeScript/JavaScript.';\n\n constructor(\n public readonly specPath: string,\n public readonly stderr?: string,\n options?: ErrorOptions,\n ) {\n const detail = stderr ? `: ${stderr}` : '';\n super(`Bundling produced no output for \"${basename(specPath)}\"${detail}`, options);\n this.name = 'SpecBundlerNoOutputError';\n }\n}\n\n/** Raised when filesystem I/O (mkdir) fails during bundling. */\nexport class SpecBundlerIoError extends Error {\n readonly code = ERROR_CODES.IO_FAILED;\n readonly recovery = 'Check filesystem permissions and available disk space.';\n\n constructor(\n public readonly operation: string,\n public readonly targetPath: string,\n options?: ErrorOptions,\n ) {\n super(`${operation} at \"${targetPath}\"`, options);\n this.name = 'SpecBundlerIoError';\n }\n}\n\nexport interface BundleResult {\n outputPath: string;\n cleanup: () => Promise<void>;\n}\n\n/** Options for bundling a spec with hooks inlined. */\nexport interface BundleWithHooksOptions {\n /** Absolute path to the spec file. */\n specPath: string;\n /** Absolute path to the run-spec-hooks.ts file. */\n hooksPath: string;\n}\n\n/** Public interface for spec bundling consumed by tools and other services. */\nexport interface ISpecBundlerService {\n /**\n * Bundles a Playwright spec file, replacing `@playwright/test` imports with a stub module.\n *\n * @param specPath - Absolute path to the spec file to bundle.\n * @throws {SpecBundlerBuildError} When the bundler reports build errors.\n * @throws {SpecBundlerNoOutputError} When bundling produces no output file.\n * @throws {SpecBundlerIoError} When filesystem operations (mkdir) fail.\n */\n bundle(specPath: string): Promise<BundleResult>;\n\n /**\n * Bundles a spec with hooks inlined so that hooks setup() executes before\n * any spec module-level code. This ensures environment variables loaded by\n * hooks (e.g. via dotenv) are available when spec modules parse config.\n *\n * @param options - Spec and hooks paths.\n */\n bundleWithHooks(options: BundleWithHooksOptions): Promise<BundleResult>;\n}\n\n/**\n * Orchestrates spec bundling using the esbuild JS API.\n */\n@injectable()\nexport class SpecBundlerService implements ISpecBundlerService {\n private readonly stubPath: string;\n /** Path to node_modules that contains playwright packages. */\n private readonly nodeModulesPath: string;\n\n constructor() {\n const serviceDir = dirname(import.meta.url.replace(FILE_URL_PREFIX, ''));\n this.stubPath = join(serviceDir, STUB_DIR, STUB_FILENAME);\n this.nodeModulesPath = this.findNodeModules(serviceDir);\n }\n\n /**\n * Walks up from the service directory to find the nearest node_modules\n * that contains playwright. Falls back to the first node_modules found.\n */\n private findNodeModules(startDir: string): string {\n let dir = startDir;\n const root = resolve('/');\n while (dir !== root) {\n const candidate = join(dir, 'node_modules');\n if (existsSync(join(candidate, 'playwright'))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n // Fallback: walk up again for any node_modules\n dir = startDir;\n while (dir !== root) {\n const candidate = join(dir, 'node_modules');\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n return join(startDir, 'node_modules');\n }\n\n /** @inheritdoc */\n async bundle(specPath: string): Promise<BundleResult> {\n const outdir = join(tmpdir(), BUNDLE.OUTDIR_NAME);\n\n try {\n await mkdir(outdir, { recursive: true });\n } catch (cause) {\n console.error(`${LOG_PREFIX} ${ERROR_MESSAGES.IO_MKDIR} \"${outdir}\":`, cause);\n throw new SpecBundlerIoError(ERROR_MESSAGES.IO_MKDIR, outdir, { cause });\n }\n\n // Symlink node_modules into the output directory so external packages\n // (playwright, playwright-core) can be resolved from the bundled file location.\n const symlinkTarget = join(outdir, 'node_modules');\n if (!existsSync(symlinkTarget)) {\n try {\n await symlink(this.nodeModulesPath, symlinkTarget, 'junction');\n } catch {\n // May fail if another process created it concurrently — safe to ignore\n }\n }\n\n return this.bundleWithEsbuild(specPath, outdir);\n }\n\n /** Bundles using the esbuild JS API. */\n private async bundleWithEsbuild(specPath: string, outdir: string): Promise<BundleResult> {\n const fileNameWithoutExt = basename(specPath).replace(/\\.[^.]+$/, '');\n const stubPath = this.stubPath;\n\n try {\n const result = await esbuildBuild({\n entryPoints: [specPath],\n outdir,\n bundle: true,\n platform: 'node',\n format: 'esm',\n target: 'node18',\n // Create a require function for CJS interop in ESM output\n banner: {\n js: \"import { createRequire as __esbuildCreateRequire } from 'module'; const require = __esbuildCreateRequire(import.meta.url);\",\n },\n external: [...BUNDLE.EXTERNALS],\n define: { [STUB_DEFINE_KEY]: JSON.stringify(stubPath) },\n plugins: [\n {\n name: BUNDLE.PLUGIN_NAME,\n setup(build) {\n build.onResolve({ filter: PLAYWRIGHT_TEST_IMPORT_FILTER }, () => ({\n path: stubPath,\n }));\n },\n },\n ],\n write: true,\n logLevel: 'silent',\n });\n\n if (result.errors.length > 0) {\n const errors = result.errors.map((e) => e.text).join('\\n');\n console.error(`${LOG_PREFIX} Build failed for \"${specPath}\": ${errors}`);\n throw new SpecBundlerBuildError(specPath, errors, { cause: new Error(errors) });\n }\n\n const outputPath = join(outdir, `${fileNameWithoutExt}.js`);\n return { outputPath, cleanup: () => this.safeUnlink(outputPath) };\n } catch (cause) {\n if (cause instanceof SpecBundlerBuildError) {\n throw cause;\n }\n const message = cause instanceof Error ? cause.message : ERROR_MESSAGES.UNEXPECTED_BUILD;\n console.error(`${LOG_PREFIX} Build failed for \"${specPath}\":`, cause);\n throw new SpecBundlerBuildError(specPath, message, { cause });\n }\n }\n\n /** @inheritdoc */\n async bundleWithHooks(options: BundleWithHooksOptions): Promise<BundleResult> {\n const outdir = join(tmpdir(), BUNDLE.OUTDIR_NAME);\n\n try {\n await mkdir(outdir, { recursive: true });\n } catch (cause) {\n console.error(`${LOG_PREFIX} ${ERROR_MESSAGES.IO_MKDIR} \"${outdir}\":`, cause);\n throw new SpecBundlerIoError(ERROR_MESSAGES.IO_MKDIR, outdir, { cause });\n }\n\n const symlinkTarget = join(outdir, 'node_modules');\n if (!existsSync(symlinkTarget)) {\n try {\n await symlink(this.nodeModulesPath, symlinkTarget, 'junction');\n } catch {\n // May fail if another process created it concurrently — safe to ignore\n }\n }\n\n return this.bundleSpecWithHooksEntry(options.specPath, options.hooksPath, outdir);\n }\n\n /**\n * Creates a virtual entry that runs hooks setup() before importing the spec,\n * then bundles everything into a single file via esbuild stdin.\n *\n * The generated entry:\n * 1. Sets __HOOKS_SOURCE_DIR__ for path resolution in hooks\n * 2. Imports and calls hooks setup() (which loads dotenv, etc.)\n * 3. Dynamically re-exports the spec so test collector picks up all tests\n *\n * Because esbuild bundles everything into one file, the hooks setup()\n * executes as top-level await BEFORE any spec module-level code runs.\n */\n private async bundleSpecWithHooksEntry(specPath: string, hooksPath: string, outdir: string): Promise<BundleResult> {\n const hooksSourceDir = dirname(resolve(hooksPath));\n const stubPath = this.stubPath;\n const outputName = 'spec-with-hooks';\n\n // Virtual entry that runs hooks before spec.\n // Uses top-level await (supported in ESM node18+).\n const entryContents = [\n `process.env.__HOOKS_SOURCE_DIR__ = ${JSON.stringify(hooksSourceDir)};`,\n `import { setup as __hooksSetup } from ${JSON.stringify(hooksPath)};`,\n `await __hooksSetup();`,\n `delete process.env.__HOOKS_SOURCE_DIR__;`,\n `export * from ${JSON.stringify(specPath)};`,\n ].join('\\n');\n\n try {\n const result = await esbuildBuild({\n stdin: {\n contents: entryContents,\n resolveDir: dirname(specPath),\n loader: 'ts',\n sourcefile: `${outputName}.ts`,\n },\n outdir,\n bundle: true,\n platform: 'node',\n format: 'esm',\n target: 'node18',\n banner: {\n js: \"import { createRequire as __esbuildCreateRequire } from 'module'; const require = __esbuildCreateRequire(import.meta.url);\",\n },\n external: [...BUNDLE.EXTERNALS],\n define: { [STUB_DEFINE_KEY]: JSON.stringify(stubPath) },\n plugins: [\n {\n name: BUNDLE.PLUGIN_NAME,\n setup(build) {\n build.onResolve({ filter: PLAYWRIGHT_TEST_IMPORT_FILTER }, () => ({\n path: stubPath,\n }));\n },\n },\n ],\n write: true,\n logLevel: 'silent',\n });\n\n if (result.errors.length > 0) {\n const errors = result.errors.map((e) => e.text).join('\\n');\n console.error(`${LOG_PREFIX} Build failed for \"${specPath}\" (with hooks): ${errors}`);\n throw new SpecBundlerBuildError(specPath, errors, { cause: new Error(errors) });\n }\n\n // esbuild stdin outputs as 'stdin.js' regardless of sourcefile\n const outputPath = join(outdir, 'stdin.js');\n return { outputPath, cleanup: () => this.safeUnlink(outputPath) };\n } catch (cause) {\n if (cause instanceof SpecBundlerBuildError) {\n throw cause;\n }\n const message = cause instanceof Error ? cause.message : ERROR_MESSAGES.UNEXPECTED_BUILD;\n console.error(`${LOG_PREFIX} Build failed for \"${specPath}\" (with hooks):`, cause);\n throw new SpecBundlerBuildError(specPath, message, { cause });\n }\n }\n\n /** Removes a file, logging on failure rather than propagating to preserve caller flow. */\n private async safeUnlink(filePath: string): Promise<void> {\n try {\n await unlink(filePath);\n } catch (cause) {\n console.warn(`${LOG_PREFIX} ${ERROR_MESSAGES.CLEANUP_FAILED} \"${filePath}\":`, cause);\n }\n }\n}\n","/**\n * SpecDiscoveryService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { existsSync } from 'node:fs';\nimport { readFile, readdir } from 'node:fs/promises';\nimport { basename, dirname, join, relative, resolve } from 'node:path';\nimport { injectable } from 'inversify';\n\n/** Information about an e2e project */\nexport interface E2EProject {\n /** Project name (directory name containing playwright.config.ts) */\n name: string;\n /** Absolute path to the project directory */\n path: string;\n /** Absolute path to the playwright config file */\n configPath: string;\n /** Test directory relative to project (default: 'tests' or as configured) */\n testDir: string;\n /** List of specs in this project */\n specs: SpecInfo[];\n}\n\n/** Information about a spec file */\nexport interface SpecInfo {\n /** Spec file name (e.g., 'auth.spec.ts') */\n name: string;\n /** Absolute path to the spec file */\n path: string;\n /** Path relative to project root */\n relativePath: string;\n /** Whether the spec exports argsSchema for parameterization */\n hasArgsSchema: boolean;\n}\n\n/** Options for filtering specs */\nexport interface FilterSpecsOptions {\n /** Filter by project name */\n projectName?: string;\n /** Filter specs by name pattern (glob-like) */\n specPattern?: string;\n}\n\nexport interface ISpecDiscoveryService {\n /**\n * Discover all e2e projects (directories with playwright.config.ts) from a base path\n */\n discoverProjects(basePath?: string): Promise<E2EProject[]>;\n\n /**\n * Get all spec files in a project's testDir\n */\n getProjectSpecs(projectPath: string): Promise<SpecInfo[]>;\n\n /**\n * Get metadata for a single spec file\n */\n getSpecInfo(specPath: string, projectPath?: string): Promise<SpecInfo>;\n\n /**\n * Filter specs across all discovered projects\n */\n filterSpecs(basePath: string, options?: FilterSpecsOptions): Promise<SpecInfo[]>;\n}\n\n@injectable()\nexport class SpecDiscoveryService implements ISpecDiscoveryService {\n /**\n * Discover all e2e projects by finding playwright.config.ts files\n */\n async discoverProjects(basePath?: string): Promise<E2EProject[]> {\n const searchPath = basePath || process.cwd();\n const projects: E2EProject[] = [];\n\n const configPaths = await this.findPlaywrightConfigs(searchPath);\n\n for (const configPath of configPaths) {\n const projectPath = dirname(configPath);\n const projectName = basename(projectPath);\n const testDir = await this.getTestDirFromConfig(configPath);\n\n const project: E2EProject = {\n name: projectName,\n path: projectPath,\n configPath,\n testDir,\n specs: [],\n };\n\n project.specs = await this.getProjectSpecs(projectPath);\n projects.push(project);\n }\n\n return projects;\n }\n\n /**\n * Get all spec files in a project's testDir\n */\n async getProjectSpecs(projectPath: string): Promise<SpecInfo[]> {\n const configPath = join(projectPath, 'playwright.config.ts');\n\n if (!existsSync(configPath)) {\n throw new Error(`No playwright.config.ts found in ${projectPath}`);\n }\n\n const testDir = await this.getTestDirFromConfig(configPath);\n const testsPath = join(projectPath, testDir);\n\n if (!existsSync(testsPath)) {\n return [];\n }\n\n const specs: SpecInfo[] = [];\n const specPaths = await this.findSpecFiles(testsPath);\n\n for (const specPath of specPaths) {\n const specInfo = await this.getSpecInfo(specPath, projectPath);\n specs.push(specInfo);\n }\n\n return specs;\n }\n\n /**\n * Get metadata for a single spec file\n */\n async getSpecInfo(specPath: string, projectPath?: string): Promise<SpecInfo> {\n const absolutePath = resolve(specPath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Spec file not found: ${specPath}`);\n }\n\n const name = basename(absolutePath);\n const relativePath = projectPath ? relative(projectPath, absolutePath) : name;\n\n const hasArgsSchema = await this.checkHasArgsSchema(absolutePath);\n\n return {\n name,\n path: absolutePath,\n relativePath,\n hasArgsSchema,\n };\n }\n\n /**\n * Filter specs across all discovered projects\n */\n async filterSpecs(basePath: string, options?: FilterSpecsOptions): Promise<SpecInfo[]> {\n const projects = await this.discoverProjects(basePath);\n let filteredSpecs: SpecInfo[] = [];\n\n for (const project of projects) {\n if (options?.projectName && project.name !== options.projectName) {\n continue;\n }\n\n let specs = project.specs;\n\n if (options?.specPattern) {\n const pattern = this.globToRegex(options.specPattern);\n specs = specs.filter((spec) => pattern.test(spec.name) || pattern.test(spec.relativePath));\n }\n\n filteredSpecs = filteredSpecs.concat(specs);\n }\n\n return filteredSpecs;\n }\n\n /**\n * Recursively find all playwright.config.ts files\n */\n private async findPlaywrightConfigs(basePath: string): Promise<string[]> {\n const configs: string[] = [];\n await this.walkDirectory(basePath, (filePath, isDir) => {\n if (isDir) {\n const dirName = basename(filePath);\n if (dirName === 'node_modules' || dirName.startsWith('.')) {\n return false;\n }\n return true;\n }\n\n if (basename(filePath) === 'playwright.config.ts') {\n configs.push(filePath);\n }\n return true;\n });\n\n return configs;\n }\n\n /**\n * Recursively find all spec files (*.spec.ts)\n */\n private async findSpecFiles(testsPath: string): Promise<string[]> {\n const specs: string[] = [];\n await this.walkDirectory(testsPath, (filePath, isDir) => {\n if (isDir) {\n if (basename(filePath) === 'node_modules') {\n return false;\n }\n return true;\n }\n\n if (filePath.endsWith('.spec.ts')) {\n specs.push(filePath);\n }\n return true;\n });\n\n return specs;\n }\n\n /**\n * Walk directory recursively with a callback\n * Callback returns false to skip traversing a directory\n */\n private async walkDirectory(\n dirPath: string,\n callback: (filePath: string, isDirectory: boolean) => boolean,\n ): Promise<void> {\n if (!existsSync(dirPath)) {\n return;\n }\n\n const entries = await readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n const shouldTraverse = callback(fullPath, true);\n if (shouldTraverse) {\n await this.walkDirectory(fullPath, callback);\n }\n } else {\n callback(fullPath, false);\n }\n }\n }\n\n /**\n * Extract testDir from playwright config file\n * Falls back to 'tests' if not found\n */\n private async getTestDirFromConfig(configPath: string): Promise<string> {\n try {\n const content = await readFile(configPath, 'utf-8');\n\n const testDirMatch = content.match(/testDir:\\s*['\"`]([^'\"`]+)['\"`]/);\n\n if (testDirMatch) {\n const testDir = testDirMatch[1];\n return testDir.replace(/^\\.\\//, '');\n }\n } catch {\n // Ignore read errors, use default\n }\n\n return 'tests';\n }\n\n /**\n * Check if a spec file exports argsSchema\n */\n private async checkHasArgsSchema(specPath: string): Promise<boolean> {\n try {\n const content = await readFile(specPath, 'utf-8');\n return /export\\s+(const\\s+)?argsSchema\\b/.test(content);\n } catch {\n return false;\n }\n }\n\n /**\n * Convert a simple glob pattern to regex\n * Supports: * (any chars), ? (single char)\n */\n private globToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n\n return new RegExp(`^${escaped}$`, 'i');\n }\n}\n","/**\n * SpecMetadataService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Single responsibility principle\n * - Dependency injection via InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on single domain)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { readFile } from 'node:fs/promises';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ArgsSchema, PlaywrightMcpConfig, SpecMetadata } from '../types/index.js';\nimport type { ISpecBundlerService } from './SpecBundlerService.js';\n\nexport interface ISpecMetadataService {\n /**\n * Extracts metadata from a spec file including argsSchema and envPrefix exports.\n * Returns defaults if exports don't exist.\n */\n extractSpecMetadata(specPath: string): Promise<SpecMetadata>;\n\n /**\n * Loads Playwright MCP configuration from playwright.config.ts.\n * Extracts the mcpConfig export if present.\n */\n loadPlaywrightConfig(configPath: string): Promise<PlaywrightMcpConfig>;\n\n /**\n * Parses environment variables matching a prefix into an args object.\n * Maps ENVPREFIX_FIELDNAME to { fieldName: value }.\n */\n parseArgsFromEnv(schema: ArgsSchema, envPrefix: string): Record<string, unknown>;\n}\n\n@injectable()\nexport class SpecMetadataService implements ISpecMetadataService {\n constructor(\n @inject(PLAYWRIGHT_TYPES.SpecBundlerService)\n private readonly bundler: ISpecBundlerService,\n ) {}\n\n /**\n * Extracts metadata from a spec file including argsSchema and envPrefix exports.\n * Bundles the spec file and dynamically imports it to extract exports.\n */\n async extractSpecMetadata(specPath: string): Promise<SpecMetadata> {\n const bundleResult = await this.bundler.bundle(specPath);\n\n try {\n // Import the bundled spec to access its exports\n const specModule = (await import(`${bundleResult.outputPath}?t=${Date.now()}`)) as {\n argsSchema?: ArgsSchema;\n envPrefix?: string;\n };\n\n const argsSchema = specModule.argsSchema ?? null;\n const envPrefix = specModule.envPrefix ?? null;\n const hasDynamicArgs = argsSchema !== null;\n\n return {\n argsSchema,\n envPrefix,\n hasDynamicArgs,\n };\n } catch {\n // Return defaults if extraction fails\n return {\n argsSchema: null,\n envPrefix: null,\n hasDynamicArgs: false,\n };\n } finally {\n await bundleResult.cleanup();\n }\n }\n\n /**\n * Loads Playwright MCP configuration from playwright.config.ts.\n * Uses regex parsing for safety instead of dynamic import.\n */\n async loadPlaywrightConfig(configPath: string): Promise<PlaywrightMcpConfig> {\n try {\n const content = await readFile(configPath, 'utf-8');\n\n const config: PlaywrightMcpConfig = {};\n\n // Extract baseURL from use.baseURL\n const baseURLMatch = content.match(/baseURL:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (baseURLMatch) {\n config.baseURL = baseURLMatch[1];\n }\n\n // Extract mcpConfig object if exported\n const mcpConfigMatch = content.match(/export\\s+const\\s+mcpConfig\\s*=\\s*\\{([^}]+)\\}/s);\n if (mcpConfigMatch) {\n const mcpBlock = mcpConfigMatch[1];\n\n // Extract setupFile\n const setupFileMatch = mcpBlock.match(/setupFile:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (setupFileMatch) {\n config.setupFile = setupFileMatch[1];\n }\n\n // Extract setupExport\n const setupExportMatch = mcpBlock.match(/setupExport:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (setupExportMatch) {\n config.setupExport = setupExportMatch[1];\n }\n }\n\n // Extract webServer config\n const webServerMatch = content.match(/webServer:\\s*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}/s);\n if (webServerMatch) {\n const webServerBlock = webServerMatch[1];\n\n const commandMatch = webServerBlock.match(/command:\\s*['\"`]([^'\"`]+)['\"`]/);\n const urlMatch = webServerBlock.match(/url:\\s*['\"`]([^'\"`]+)['\"`]/);\n const reuseMatch = webServerBlock.match(/reuseExistingServer:\\s*(true|false)/);\n const timeoutMatch = webServerBlock.match(/timeout:\\s*(\\d+)/);\n const cwdMatch = webServerBlock.match(/cwd:\\s*['\"`]([^'\"`]+)['\"`]/);\n\n if (commandMatch && urlMatch) {\n config.webServer = {\n command: commandMatch[1],\n url: urlMatch[1],\n reuseExistingServer: reuseMatch ? reuseMatch[1] === 'true' : undefined,\n timeout: timeoutMatch ? Number.parseInt(timeoutMatch[1], 10) : undefined,\n cwd: cwdMatch ? cwdMatch[1] : undefined,\n };\n }\n }\n\n return config;\n } catch {\n // Return empty config if file cannot be read\n return {};\n }\n }\n\n /**\n * Parses environment variables matching a prefix into an args object.\n * Converts ENVPREFIX_FIELD_NAME to { fieldName: value }.\n *\n * Example:\n * - envPrefix: 'SPEC_LOGIN_'\n * - env: { SPEC_LOGIN_USERNAME: 'test', SPEC_LOGIN_PASSWORD: 'secret' }\n * - result: { username: 'test', password: 'secret' }\n */\n parseArgsFromEnv(schema: ArgsSchema, envPrefix: string): Record<string, unknown> {\n const args: Record<string, unknown> = {};\n const prefix = envPrefix.toUpperCase();\n\n for (const [key, value] of Object.entries(process.env)) {\n if (key.startsWith(prefix) && value !== undefined) {\n // Remove prefix and convert to camelCase\n const fieldName = this.envKeyToFieldName(key.slice(prefix.length));\n args[fieldName] = this.parseEnvValue(value);\n }\n }\n\n // Validate against schema if provided\n try {\n return schema.parse(args) as Record<string, unknown>;\n } catch {\n // Return parsed args even if validation fails\n // Caller can handle validation errors\n return args;\n }\n }\n\n /**\n * Converts SNAKE_CASE environment variable suffix to camelCase field name.\n * Example: USER_NAME -> userName\n */\n private envKeyToFieldName(envKey: string): string {\n return envKey\n .toLowerCase()\n .split('_')\n .map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))\n .join('');\n }\n\n /**\n * Parses environment variable value to appropriate type.\n * Handles booleans, numbers, and JSON objects.\n */\n private parseEnvValue(value: string): unknown {\n // Boolean\n if (value.toLowerCase() === 'true') return true;\n if (value.toLowerCase() === 'false') return false;\n\n // Number\n const num = Number(value);\n if (!Number.isNaN(num) && value.trim() !== '') return num;\n\n // JSON object/array\n if ((value.startsWith('{') && value.endsWith('}')) || (value.startsWith('[') && value.endsWith(']'))) {\n try {\n return JSON.parse(value);\n } catch {\n // Not valid JSON, return as string\n }\n }\n\n // Default to string\n return value;\n }\n}\n","/**\n * SpecRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for spec file loading and execution\n * - Dependency injection via InversifyJS\n * - Single responsibility: spec loading and test execution\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Bundle spec files to alias @playwright/test imports\n * - Collect test blocks using PlaywrightTestStub collector\n *\n * AVOID:\n * - Mixing spec execution with browser management (delegate to services)\n * - Global state for spec sessions\n */\n\nimport 'reflect-metadata/lite';\nimport { inject, injectable } from 'inversify';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport playwright from 'playwright';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport {\n getCollectedSuite,\n initCollector,\n resetCollector,\n SkipTestError,\n type TestBlock,\n type TestFixtures,\n type TestSuite,\n} from '../stubs/playwright-test.js';\nimport type { TestFilter } from '../types/index.js';\nimport type { ISpecBundlerService } from './SpecBundlerService.js';\n\n/**\n * Result of a single test execution.\n */\nexport interface TestResult {\n /** Test title */\n title: string;\n /** Full test title including describe blocks */\n fullTitle: string;\n /** Test passed */\n passed: boolean;\n /** Error message if failed */\n error: string | null;\n /** Execution duration in ms */\n duration: number;\n}\n\n/**\n * Result of executing a spec file.\n */\nexport interface SpecExecutionResult {\n /** Spec file path */\n specPath: string;\n /** Total tests executed */\n totalTests: number;\n /** Tests passed */\n passed: number;\n /** Tests failed */\n failed: number;\n /** Individual test results */\n testResults: TestResult[];\n /** Overall success */\n success: boolean;\n /** Total execution duration in ms */\n duration: number;\n}\n\n/**\n * Options for executing a spec file with fixtures.\n */\nexport interface SpecExecuteOptions {\n /** Spec file path to execute */\n specPath: string;\n /** Session ID for pause control */\n sessionId: string;\n /** Playwright Page instance */\n page: Page;\n /** Playwright BrowserContext */\n context: BrowserContext;\n /** Playwright Browser (optional for extension mode which uses persistent context) */\n browser?: Browser;\n /** Optional hooks file path to bundle with the spec */\n hooksPath?: string;\n /** Base URL for per-test context creation */\n baseURL?: string;\n}\n\n/**\n * Extended fixtures that include spec arguments.\n */\nexport interface TestFixturesEnhanced extends TestFixtures {\n /** Arguments passed to the spec */\n specArgs?: Record<string, unknown>;\n}\n\n/**\n * Enhanced options for executing a spec with filtering and arguments.\n */\nexport interface SpecExecuteOptionsEnhanced extends SpecExecuteOptions {\n /** Filter to select which tests to run */\n testFilter?: TestFilter;\n /** Arguments to pass to the spec */\n specArgs?: Record<string, unknown>;\n}\n\nexport interface ISpecRunner {\n /**\n * Loads a spec file and collects test blocks.\n * When hooksPath is provided, hooks setup() runs inside the bundle\n * before any spec module-level code executes.\n */\n loadSpec(specPath: string, hooksPath?: string): Promise<TestSuite>;\n\n /**\n * Executes a loaded spec with the provided fixtures.\n */\n executeSpec(options: SpecExecuteOptions): Promise<SpecExecutionResult>;\n\n /**\n * Executes a spec with filtering and enhanced options.\n */\n executeSpecEnhanced(options: SpecExecuteOptionsEnhanced): Promise<SpecExecutionResult>;\n}\n\n@injectable()\nexport class SpecRunner implements ISpecRunner {\n constructor(\n @inject(PLAYWRIGHT_TYPES.SpecBundlerService)\n private readonly bundler: ISpecBundlerService,\n ) {}\n\n /**\n * Loads a spec file and collects test blocks using PlaywrightTestStub.\n * Bundles the spec file to alias @playwright/test imports to our stub.\n * When hooksPath is provided, hooks are bundled into the same output\n * so setup() runs before any spec module-level code.\n */\n async loadSpec(specPath: string, hooksPath?: string): Promise<TestSuite> {\n const bundleResult = hooksPath\n ? await this.bundler.bundleWithHooks({ specPath, hooksPath })\n : await this.bundler.bundle(specPath);\n\n try {\n initCollector(specPath);\n\n // Temporarily clear Playwright's double-load guard so the bundled stub\n // can import playwright/test without triggering \"Requiring @playwright/test second time\"\n const proc = process as unknown as Record<string, unknown>;\n const savedInitiator = proc.__pw_initiator__;\n proc.__pw_initiator__ = undefined;\n\n // NOTE: Dynamic import required - bundled spec is generated at runtime in temp directory\n await import(`${bundleResult.outputPath}?t=${Date.now()}`);\n\n proc.__pw_initiator__ = savedInitiator;\n\n return getCollectedSuite();\n } catch (error) {\n resetCollector();\n throw new Error(`Failed to load spec \"${specPath}\": ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n } finally {\n await bundleResult.cleanup();\n }\n }\n\n /**\n * Executes a loaded spec with the provided fixtures.\n */\n async executeSpec(options: SpecExecuteOptions): Promise<SpecExecutionResult> {\n const { specPath, browser, hooksPath, baseURL } = options;\n\n const startTime = Date.now();\n const testResults: TestResult[] = [];\n let passed = 0;\n let failed = 0;\n\n try {\n const suite = await this.loadSpec(specPath, hooksPath);\n\n for (const test of suite.allTests) {\n // Create a fresh context and page per test to match Playwright's isolation model\n const testContext = await browser!.newContext(baseURL ? { baseURL } : {});\n const testPage = await testContext.newPage();\n const fixtures: TestFixtures = { page: testPage, context: testContext, browser, playwright };\n\n const testStartTime = Date.now();\n\n try {\n await test.fn(fixtures);\n\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: Date.now() - testStartTime,\n });\n passed++;\n } catch (error) {\n if (error instanceof SkipTestError || (error instanceof Error && 'isSkip' in error)) {\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: Date.now() - testStartTime,\n });\n passed++;\n } else {\n const errorMessage = error instanceof Error ? error.message : String(error);\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: false,\n error: errorMessage,\n duration: Date.now() - testStartTime,\n });\n failed++;\n }\n } finally {\n await testContext.close().catch(() => {\n /* context may already be closed */\n });\n }\n }\n\n return {\n specPath,\n totalTests: suite.testCount,\n passed,\n failed,\n testResults,\n success: failed === 0,\n duration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n specPath,\n totalTests: 0,\n passed: 0,\n failed: 1,\n testResults: [\n {\n title: 'Spec Loading',\n fullTitle: `Failed to load: ${specPath}`,\n passed: false,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n },\n ],\n success: false,\n duration: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Executes a spec with filtering and enhanced options.\n * Filters tests based on TestFilter criteria and injects specArgs into fixtures.\n */\n async executeSpecEnhanced(options: SpecExecuteOptionsEnhanced): Promise<SpecExecutionResult> {\n const { specPath, browser, testFilter, specArgs, hooksPath, baseURL } = options;\n\n const startTime = Date.now();\n const testResults: TestResult[] = [];\n let passed = 0;\n let failed = 0;\n\n try {\n const suite = await this.loadSpec(specPath, hooksPath);\n\n // Filter tests based on criteria\n const testsToRun = testFilter ? this.filterTests(suite.allTests, testFilter) : suite.allTests;\n\n for (const test of testsToRun) {\n // Create a fresh context and page per test to match Playwright's isolation model\n const testContext = await browser!.newContext(baseURL ? { baseURL } : {});\n const testPage = await testContext.newPage();\n const fixtures: TestFixturesEnhanced = { page: testPage, context: testContext, browser, playwright, specArgs };\n\n // Skip tests marked with skip flag\n if (test.skip) {\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: 0,\n });\n passed++;\n await testContext.close().catch(() => {\n /* context may already be closed */\n });\n continue;\n }\n\n const testStartTime = Date.now();\n\n try {\n await test.fn(fixtures);\n\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: Date.now() - testStartTime,\n });\n passed++;\n } catch (error) {\n if (error instanceof SkipTestError || (error instanceof Error && 'isSkip' in error)) {\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: true,\n error: null,\n duration: Date.now() - testStartTime,\n });\n passed++;\n } else {\n const errorMessage = error instanceof Error ? error.message : String(error);\n testResults.push({\n title: test.title,\n fullTitle: test.fullTitle,\n passed: false,\n error: errorMessage,\n duration: Date.now() - testStartTime,\n });\n failed++;\n }\n } finally {\n await testContext.close().catch(() => {\n /* context may already be closed */\n });\n }\n }\n\n return {\n specPath,\n totalTests: testsToRun.length,\n passed,\n failed,\n testResults,\n success: failed === 0,\n duration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n specPath,\n totalTests: 0,\n passed: 0,\n failed: 1,\n testResults: [\n {\n title: 'Spec Loading',\n fullTitle: `Failed to load: ${specPath}`,\n passed: false,\n error: error instanceof Error ? error.message : String(error),\n duration: Date.now() - startTime,\n },\n ],\n success: false,\n duration: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Filters tests based on TestFilter criteria.\n * Filtering logic:\n * - If onlyMarked, prefer tests with only=true (if any exist)\n * - Filter by exact testName match\n * - Filter by testPattern regex on fullTitle\n * - Filter by describeFilter regex on fullTitle\n */\n private filterTests(tests: TestBlock[], filter: TestFilter): TestBlock[] {\n let filtered = [...tests];\n\n // If onlyMarked is true, prefer tests marked with only=true\n if (filter.onlyMarked) {\n const onlyTests = filtered.filter((t) => t.only);\n if (onlyTests.length > 0) {\n filtered = onlyTests;\n }\n }\n\n // Filter by exact test name\n if (filter.testName) {\n filtered = filtered.filter((t) => t.title === filter.testName);\n }\n\n // Filter by test pattern (regex on fullTitle)\n if (filter.testPattern) {\n const pattern = new RegExp(filter.testPattern, 'i');\n filtered = filtered.filter((t) => pattern.test(t.fullTitle));\n }\n\n // Filter by describe block pattern\n if (filter.describeFilter) {\n const pattern = new RegExp(filter.describeFilter, 'i');\n filtered = filtered.filter((t) => pattern.test(t.fullTitle));\n }\n\n return filtered;\n }\n}\n","/**\n * StealthLauncher\n *\n * DESIGN PATTERNS:\n * - Service pattern for stealth browser launching\n * - No Playwright/CDP - uses native Chrome process spawning\n * - Fully undetectable by bot detection systems\n *\n * CODING STANDARDS:\n * - Use child_process.spawn for Chrome launch\n * - Async/await for all operations\n * - Proper cleanup on process termination\n *\n * AVOID:\n * - Playwright APIs (defeats stealth purpose)\n * - CDP connections\n * - Automation flags\n */\n\nimport 'reflect-metadata/lite';\nimport { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { killProcessTree } from '../utils/processTree.js';\nimport { type ProxyConfig, buildProxyArgs, createProxyAuthExtension } from '../utils/proxyAuthExtension.js';\nimport type { IBrowserLockManager } from './BrowserLockManager.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport type { IProfileService } from './ProfileService.js';\n\nexport interface StealthLaunchOptions {\n /** Path to Chrome extension directory */\n extensionPath?: string;\n /** Initial URL to navigate to */\n url?: string;\n /** Profile name for persistent user data */\n profileName?: string;\n /** Additional Chrome arguments */\n extraArgs?: string[];\n /** Window size */\n windowSize?: { width: number; height: number };\n /** Proxy configuration for routing traffic through a proxy server */\n proxy?: ProxyConfig;\n}\n\nexport interface StealthBrowserInstance {\n /** Unique browser ID */\n id: string;\n /** Chrome process handle */\n process: ChildProcess;\n /** Process ID */\n pid: number;\n /** User data directory path */\n userDataDir: string;\n /** Extension path used */\n extensionPath: string;\n /** Profile name if specified */\n profileName?: string;\n /** Creation timestamp */\n createdAt: Date;\n /** Whether browser is still running */\n isRunning: boolean;\n}\n\nexport interface IStealthLauncher {\n launch(options?: StealthLaunchOptions): Promise<StealthBrowserInstance>;\n close(browserId: string): Promise<void>;\n closeAll(): Promise<void>;\n getBrowser(id: string): StealthBrowserInstance | undefined;\n listBrowsers(): StealthBrowserInstance[];\n findChromePath(): string | null;\n}\n\n@injectable()\nexport class StealthLauncher implements IStealthLauncher {\n private browsers: Map<string, StealthBrowserInstance> = new Map();\n private browserIdCounter = 0;\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.ProfileService) private profileService: IProfileService,\n @inject(PLAYWRIGHT_TYPES.PageRegistry) private pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserLockManager) private lockManager: IBrowserLockManager,\n ) {}\n\n /**\n * Find Chrome executable path based on OS\n */\n findChromePath(): string | null {\n const platform = os.platform();\n\n const paths: Record<string, string[]> = {\n darwin: [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',\n `${os.homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,\n ],\n win32: [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n ],\n linux: [\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium',\n '/usr/bin/chromium-browser',\n '/snap/bin/chromium',\n ],\n };\n\n const candidates = paths[platform] ?? paths.linux;\n\n for (const chromePath of candidates) {\n if (fs.existsSync(chromePath)) {\n return chromePath;\n }\n }\n\n return null;\n }\n\n /**\n * Resolve extension path with multiple fallback strategies\n */\n private resolveExtensionPath(providedPath?: string): string {\n if (providedPath && fs.existsSync(path.join(providedPath, 'manifest.json'))) {\n return providedPath;\n }\n\n // Strategy 1: Resolve relative to this module (dist/extension)\n const currentDir = path.dirname(new URL(import.meta.url).pathname);\n const candidates = [\n path.resolve(currentDir, '../dist/extension'),\n path.resolve(currentDir, '../../dist/extension'),\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(path.join(candidate, 'manifest.json'))) {\n return candidate;\n }\n }\n\n // Strategy 2: Try cwd + dist/extension (for CLI execution)\n const cwdExtPath = path.resolve(process.cwd(), 'dist/extension');\n if (fs.existsSync(path.join(cwdExtPath, 'manifest.json'))) {\n return cwdExtPath;\n }\n\n throw new Error('Chrome extension not found. Build the extension first: pnpm build:extension');\n }\n\n /**\n * Create a user data directory for Chrome\n */\n private createUserDataDir(profileName?: string): string {\n if (profileName) {\n const profileDir = path.join(this.profileService.getProfilesDir(), profileName, 'stealth-user-data');\n fs.mkdirSync(profileDir, { recursive: true });\n return profileDir;\n }\n\n const tempDir = path.join(os.tmpdir(), `stealth-chrome-${Date.now()}`);\n fs.mkdirSync(tempDir, { recursive: true });\n return tempDir;\n }\n\n /**\n * Launch Chrome in stealth mode - no Playwright, no CDP, no automation flags\n */\n async launch(options: StealthLaunchOptions = {}): Promise<StealthBrowserInstance> {\n const chromePath = this.findChromePath();\n if (!chromePath) {\n throw new Error(\n 'Chrome not found. Please install Google Chrome.\\n' +\n 'macOS: brew install --cask google-chrome\\n' +\n 'Linux: sudo apt install google-chrome-stable',\n );\n }\n\n const extensionPath = this.resolveExtensionPath(options.extensionPath);\n const userDataDir = this.createUserDataDir(options.profileName);\n\n // Check for existing browser session locks before launching\n await this.lockManager.ensureAvailable(userDataDir);\n\n const browserId = `stealth-browser-${++this.browserIdCounter}`;\n\n // If proxy auth is needed, generate a temp extension to handle credentials\n const extensionPaths = [extensionPath];\n if (options.proxy?.username && options.proxy?.password) {\n extensionPaths.push(createProxyAuthExtension(options.proxy.username, options.proxy.password));\n }\n\n const loadExtensions = extensionPaths.join(',');\n\n // Build Chrome arguments for maximum stealth\n const args: string[] = [\n // User data directory (required for extension persistence)\n `--user-data-dir=${userDataDir}`,\n\n // Extension loading\n `--load-extension=${loadExtensions}`,\n `--disable-extensions-except=${loadExtensions}`,\n\n // Stealth flags - remove all automation indicators\n '--disable-blink-features=AutomationControlled',\n '--disable-infobars',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-background-timer-throttling',\n '--disable-backgrounding-occluded-windows',\n '--disable-renderer-backgrounding',\n '--disable-component-update',\n\n // Remove automation specific flags\n '--disable-features=TranslateUI',\n '--disable-ipc-flooding-protection',\n '--password-store=basic',\n '--use-mock-keychain',\n\n // Performance and stability\n '--disable-dev-shm-usage',\n '--disable-gpu-sandbox',\n\n // Window settings\n ...(options.windowSize\n ? [`--window-size=${options.windowSize.width},${options.windowSize.height}`]\n : ['--window-size=1280,720']),\n\n // Proxy configuration\n ...(options.proxy ? buildProxyArgs(options.proxy) : []),\n\n // Start with a specific URL or blank page\n options.url ?? 'about:blank',\n\n // Additional user-specified args\n ...(options.extraArgs ?? []),\n ];\n\n // Spawn Chrome process — stdout/stderr set to 'ignore' to prevent\n // buffer backpressure from blocking the Node process\n const chromeProcess = spawn(chromePath, args, {\n detached: false,\n stdio: ['ignore', 'ignore', 'ignore'],\n env: {\n ...process.env,\n DISPLAY: process.env.DISPLAY,\n },\n });\n\n if (!chromeProcess.pid) {\n throw new Error('Failed to start Chrome process');\n }\n\n const instance: StealthBrowserInstance = {\n id: browserId,\n process: chromeProcess,\n pid: chromeProcess.pid,\n userDataDir,\n extensionPath,\n profileName: options.profileName,\n createdAt: new Date(),\n isRunning: true,\n };\n\n // Track process lifecycle\n chromeProcess.on('exit', () => {\n instance.isRunning = false;\n });\n\n chromeProcess.on('error', (err) => {\n console.error(`Chrome process ${browserId} error:`, err);\n instance.isRunning = false;\n });\n\n this.browsers.set(browserId, instance);\n\n // Register a virtual page entry for this browser (extension mode - no Playwright objects)\n this.pageRegistry.registerExtensionPage(browserId);\n\n return instance;\n }\n\n /**\n * Close a specific browser instance\n */\n async close(browserId: string): Promise<void> {\n const instance = this.browsers.get(browserId);\n if (!instance) {\n return;\n }\n\n if (instance.isRunning && instance.pid) {\n // Kill entire process tree to ensure Chrome helper processes\n // (GPU, renderer) are also terminated. Without tree kill,\n // child processes survive and the Chrome icon lingers in the macOS dock.\n await killProcessTree(instance.pid);\n }\n\n instance.isRunning = false;\n this.browsers.delete(browserId);\n\n // Clean up temp user data dir if not using a named profile\n if (!instance.profileName && instance.userDataDir.includes('stealth-chrome-')) {\n try {\n fs.rmSync(instance.userDataDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n\n /**\n * Close all browser instances\n */\n async closeAll(): Promise<void> {\n const closePromises = Array.from(this.browsers.keys()).map((id) => this.close(id));\n await Promise.all(closePromises);\n }\n\n /**\n * Get a browser instance by ID\n */\n getBrowser(id: string): StealthBrowserInstance | undefined {\n return this.browsers.get(id);\n }\n\n /**\n * List all active browser instances\n */\n listBrowsers(): StealthBrowserInstance[] {\n return Array.from(this.browsers.values()).filter((b) => b.isRunning);\n }\n}\n","/**\n * ToolExecutor\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Strategy pattern for mode-aware execution\n * - Dependency injection via InversifyJS\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Keep methods focused and well-named\n * - Document complex logic with comments\n *\n * AVOID:\n * - Mixing concerns (keep focused on execution routing)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from './BrowserService.js';\nimport type { ExtensionToolDelegator } from './ExtensionToolDelegator.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport { TelemetryService, type ITelemetryService } from './TelemetryService.js';\n\nfunction getResultError(result: CallToolResult): string | undefined {\n if (!result.isError) {\n return undefined;\n }\n\n const firstContent = result.content[0];\n if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n return firstContent.text;\n }\n\n return 'Unknown tool execution error';\n}\n\n/**\n * Service for mode-aware tool execution.\n * Routes tool calls to either Playwright or Chrome extension based on page mode.\n */\n@injectable()\nexport class ToolExecutor {\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) private pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) private browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ExtensionToolDelegator) private delegator: ExtensionToolDelegator,\n @inject(PLAYWRIGHT_TYPES.TelemetryService) private telemetry: ITelemetryService = new TelemetryService(),\n ) {}\n\n /**\n * Execute a tool with mode-aware routing.\n * If the page is in extension mode, delegates to the extension.\n * Otherwise, executes the provided Playwright function.\n *\n * @param toolName - The name of the tool being executed\n * @param pageId - The page ID to operate on\n * @param args - Additional arguments for the tool\n * @param playwrightExecutor - Function to execute for Playwright mode\n * @returns The tool execution result\n */\n async execute(\n toolName: string,\n pageId: string,\n args: Record<string, unknown>,\n playwrightExecutor: () => Promise<CallToolResult>,\n ): Promise<CallToolResult> {\n return this.telemetry.runInSpan(\n 'browse_tool.tool_executor.page',\n {\n attributes: {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.page.id': pageId,\n },\n },\n async (span) => {\n const pageEntry = this.pageRegistry.get(pageId);\n\n if (!pageEntry) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Page \"${pageId}\" not found` });\n return {\n content: [{ type: 'text', text: `Page \"${pageId}\" not found` }],\n isError: true,\n };\n }\n\n span?.setAttributes({\n 'browse_tool.browser.id': pageEntry.browserId,\n 'browse_tool.execution.mode': pageEntry.mode,\n });\n\n const result =\n pageEntry.mode === 'extension'\n ? await this.delegator.executeTool(toolName, { pageId, browserId: pageEntry.browserId, ...args })\n : await playwrightExecutor();\n\n const errorText = getResultError(result);\n if (errorText) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorText });\n } else if (pageEntry.mode === 'extension') {\n try {\n await this.browserService.ensureExtensionRecordingActive(pageEntry.browserId, pageId);\n } catch (error) {\n console.warn(\n `[ToolExecutor] Failed to start extension recording for browser \"${pageEntry.browserId}\" on page \"${pageId}\":`,\n error,\n );\n }\n }\n\n return result;\n },\n );\n }\n\n /**\n * Check if a page is in extension mode.\n * @param pageId - The page ID to check\n * @returns True if the page is in extension mode\n */\n isExtensionMode(pageId: string): boolean {\n const pageEntry = this.pageRegistry.get(pageId);\n return pageEntry?.mode === 'extension';\n }\n\n /**\n * Execute a browser-level tool with mode-aware routing.\n * If the browser is in extension mode, delegates to the extension.\n * Otherwise, executes the provided Playwright function.\n *\n * @param toolName - The name of the tool being executed\n * @param browserId - The browser ID to operate on\n * @param args - Additional arguments for the tool\n * @param playwrightExecutor - Function to execute for Playwright mode\n * @returns The tool execution result\n */\n async executeForBrowser(\n toolName: string,\n browserId: string,\n args: Record<string, unknown>,\n playwrightExecutor: () => Promise<CallToolResult>,\n ): Promise<CallToolResult> {\n return this.telemetry.runInSpan(\n 'browse_tool.tool_executor.browser',\n {\n attributes: {\n 'browse_tool.tool.name': toolName,\n 'browse_tool.browser.id': browserId,\n },\n },\n async (span) => {\n const browser = this.browserService.getBrowser(browserId);\n\n if (!browser) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: `Browser \"${browserId}\" not found` });\n return {\n content: [{ type: 'text', text: `Browser \"${browserId}\" not found` }],\n isError: true,\n };\n }\n\n span?.setAttributes({\n 'browse_tool.execution.mode': browser.mode,\n });\n\n const result =\n browser.mode === 'extension' || browser.mode === 'vm'\n ? await this.delegator.executeTool(toolName, { browserId, ...args })\n : await playwrightExecutor();\n\n const errorText = getResultError(result);\n if (errorText) {\n span?.setStatus({ code: SpanStatusCode.ERROR, message: errorText });\n }\n\n return result;\n },\n );\n }\n\n /**\n * Check if a browser is in extension mode.\n * @param browserId - The browser ID to check\n * @returns True if the browser is in extension mode\n */\n isBrowserExtensionMode(browserId: string): boolean {\n const browser = this.browserService.getBrowser(browserId);\n return browser?.mode === 'extension' || browser?.mode === 'vm';\n }\n}\n","/**\n * WebServerManager\n *\n * DESIGN PATTERNS:\n * - Service pattern for dev server lifecycle management\n * - Single responsibility: server start/stop/status\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Throw descriptive errors for error cases\n * - Handle process cleanup on shutdown\n *\n * AVOID:\n * - Mixing concerns (keep focused on server management)\n * - Direct tool implementation (services should be tool-agnostic)\n */\n\nimport 'reflect-metadata/lite';\nimport { type ChildProcess, spawn } from 'node:child_process';\nimport { resolve } from 'node:path';\nimport { injectable } from 'inversify';\nimport type { WebServerConfig } from '../types/index.js';\n\n/** Result of starting a server */\nexport interface ServerStartResult {\n /** Whether the server was started (false if reused existing) */\n started: boolean;\n /** URL the server is running on */\n url: string;\n /** Whether an existing server was reused */\n reused: boolean;\n}\n\nexport interface IWebServerManager {\n /**\n * Starts the dev server using the provided configuration.\n * If reuseExistingServer is true and server is already running, skips start.\n */\n startServer(config: WebServerConfig, configDir: string): Promise<ServerStartResult>;\n\n /**\n * Waits for the server to be ready by polling the URL.\n * Returns true when server responds, false on timeout.\n */\n waitForServer(url: string, timeout: number): Promise<boolean>;\n\n /**\n * Stops the currently running server process.\n */\n stopServer(): Promise<void>;\n\n /**\n * Checks if a server is already running at the given URL.\n */\n isServerRunning(url: string): Promise<boolean>;\n\n /**\n * Gets the URL of the currently managed server, if any.\n */\n getCurrentUrl(): string | null;\n}\n\n@injectable()\nexport class WebServerManager implements IWebServerManager {\n private serverProcess: ChildProcess | null = null;\n private currentUrl: string | null = null;\n private cleanupRegistered = false;\n\n constructor() {\n this.registerCleanup();\n }\n\n /**\n * Registers cleanup handlers only once to avoid memory leaks.\n */\n private registerCleanup(): void {\n if (this.cleanupRegistered) {\n return;\n }\n this.cleanupRegistered = true;\n\n const cleanup = () => this.cleanupSync();\n process.on('exit', cleanup);\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n }\n\n /**\n * Starts the dev server using spawn.\n * Resolves cwd relative to configDir if provided.\n */\n async startServer(config: WebServerConfig, configDir: string): Promise<ServerStartResult> {\n const { command, url, reuseExistingServer = false, timeout = 30000, cwd } = config;\n\n // Check if server is already running\n if (reuseExistingServer) {\n const running = await this.isServerRunning(url);\n if (running) {\n this.currentUrl = url;\n return { started: false, url, reused: true };\n }\n }\n\n // Stop any existing server we started\n await this.stopServer();\n\n // Resolve working directory\n const workingDir = cwd ? resolve(configDir, cwd) : configDir;\n\n // Parse command into parts\n const [cmd, ...args] = this.parseCommand(command);\n\n // Spawn the server process\n this.serverProcess = spawn(cmd, args, {\n cwd: workingDir,\n shell: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n });\n\n this.currentUrl = url;\n\n // Handle process errors\n this.serverProcess.on('error', (error) => {\n console.error(`Server process error: ${error.message}`);\n });\n\n // Wait for server to be ready\n const ready = await this.waitForServer(url, timeout);\n if (!ready) {\n await this.stopServer();\n throw new Error(`Server failed to start within ${timeout}ms. URL: ${url}, Command: ${command}`);\n }\n\n return { started: true, url, reused: false };\n }\n\n /**\n * Polls the URL until it responds with a 2xx/3xx status.\n * Uses exponential backoff starting at 100ms.\n */\n async waitForServer(url: string, timeout: number): Promise<boolean> {\n const startTime = Date.now();\n let delay = 100;\n const maxDelay = 2000;\n\n while (Date.now() - startTime < timeout) {\n const running = await this.isServerRunning(url);\n if (running) {\n return true;\n }\n\n // Wait before next poll with exponential backoff\n await this.sleep(delay);\n delay = Math.min(delay * 1.5, maxDelay);\n }\n\n return false;\n }\n\n /**\n * Stops the server process if one is running.\n */\n async stopServer(): Promise<void> {\n if (!this.serverProcess) {\n return;\n }\n\n return new Promise<void>((resolvePromise) => {\n if (!this.serverProcess) {\n resolvePromise();\n return;\n }\n\n const proc = this.serverProcess;\n this.serverProcess = null;\n this.currentUrl = null;\n\n // Set timeout for force kill\n const forceKillTimeout = setTimeout(() => {\n try {\n proc.kill('SIGKILL');\n } catch {\n // Process might already be dead\n }\n resolvePromise();\n }, 5000);\n\n proc.on('exit', () => {\n clearTimeout(forceKillTimeout);\n resolvePromise();\n });\n\n // Try graceful shutdown first\n try {\n proc.kill('SIGTERM');\n } catch {\n clearTimeout(forceKillTimeout);\n resolvePromise();\n }\n });\n }\n\n /**\n * Checks if a server is responding at the given URL.\n * Returns true for 2xx and 3xx status codes.\n */\n async isServerRunning(url: string): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 3000);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Accept 2xx and 3xx responses as \"running\"\n return response.status >= 200 && response.status < 400;\n } catch {\n return false;\n }\n }\n\n /**\n * Parses a command string into executable and arguments.\n * Handles quoted arguments.\n */\n private parseCommand(command: string): string[] {\n // Simple parsing - split on spaces but respect quotes\n const parts: string[] = [];\n let current = '';\n let inQuote = false;\n let quoteChar = '';\n\n for (const char of command) {\n if ((char === '\"' || char === \"'\") && !inQuote) {\n inQuote = true;\n quoteChar = char;\n } else if (char === quoteChar && inQuote) {\n inQuote = false;\n quoteChar = '';\n } else if (char === ' ' && !inQuote) {\n if (current) {\n parts.push(current);\n current = '';\n }\n } else {\n current += char;\n }\n }\n\n if (current) {\n parts.push(current);\n }\n\n return parts;\n }\n\n /**\n * Helper to sleep for a given number of milliseconds.\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Gets the URL of the currently managed server.\n */\n getCurrentUrl(): string | null {\n return this.currentUrl;\n }\n\n /**\n * Synchronous cleanup for process exit handlers.\n */\n private cleanupSync(): void {\n if (this.serverProcess) {\n try {\n this.serverProcess.kill('SIGKILL');\n } catch {\n // Process might already be dead\n }\n this.serverProcess = null;\n this.currentUrl = null;\n }\n }\n}\n","/**\n * Extension WebSocket Message Protocol\n *\n * DESIGN PATTERNS:\n * - Zod schemas for runtime validation of WebSocket messages\n * - Discriminated unions for type-safe message handling\n * - Shared protocol between server and extension\n *\n * CODING STANDARDS:\n * - Use Zod for schema definition and validation\n * - Export both schemas and inferred types\n * - Use discriminated unions with 'type' field\n *\n * AVOID:\n * - Duplicating type definitions\n * - Manual type guards (use Zod parse)\n * - Inconsistent message structure\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Content Schemas (shared by task results)\n// ============================================================================\n\nexport const ContentItemSchema = z.object({\n type: z.string(),\n text: z.string().optional(),\n data: z.string().optional(),\n mimeType: z.string().optional(),\n});\n\nexport const TaskTelemetryContextSchema = z.object({\n traceId: z\n .string()\n .regex(/^[0-9a-f]{32}$/i)\n .optional(),\n parentSpanId: z\n .string()\n .regex(/^[0-9a-f]{16}$/i)\n .optional(),\n});\n\nexport const TaskResultContentSchema = z.object({\n content: z.array(ContentItemSchema),\n isError: z.boolean().optional(),\n});\n\n// ============================================================================\n// Server → Extension Messages\n// ============================================================================\n\n/**\n * Push a task to the extension for execution\n */\nexport const TaskPushSchema = z.object({\n type: z.literal('task:push'),\n id: z.string(),\n payload: z.object({\n taskId: z.string(),\n tool: z.string(),\n arguments: z.record(z.string(), z.unknown()),\n pageId: z.string().optional(),\n telemetry: TaskTelemetryContextSchema.optional(),\n }),\n});\n\n/**\n * Notify extension that a page was created on the server\n */\nexport const PageCreatedSchema = z.object({\n type: z.literal('page:created'),\n payload: z.object({\n pageId: z.string(),\n browserId: z.string(),\n url: z.string().optional(),\n }),\n});\n\n/**\n * Notify extension that a page was removed on the server\n */\nexport const PageRemovedSchema = z.object({\n type: z.literal('page:removed'),\n payload: z.object({\n pageId: z.string(),\n }),\n});\n\n/**\n * Acknowledge session registration\n */\nexport const SessionAckSchema = z.object({\n type: z.literal('session:ack'),\n id: z.string(),\n payload: z.object({\n sessionId: z.string(),\n controlMode: z.string(),\n }),\n});\n\n/**\n * Pong response to ping\n */\nexport const PongSchema = z.object({\n type: z.literal('pong'),\n});\n\n/**\n * Error message from server\n */\nexport const ServerErrorSchema = z.object({\n type: z.literal('error'),\n payload: z.object({\n message: z.string(),\n code: z.string().optional(),\n }),\n});\n\n/**\n * All possible messages from server to extension\n */\nexport const ServerMessageSchema = z.discriminatedUnion('type', [\n TaskPushSchema,\n PageCreatedSchema,\n PageRemovedSchema,\n SessionAckSchema,\n PongSchema,\n ServerErrorSchema,\n]);\n\n// ============================================================================\n// Extension → Server Messages\n// ============================================================================\n\n/**\n * Register extension session with server\n */\nexport const SessionRegisterSchema = z.object({\n type: z.literal('session:register'),\n id: z.string(),\n payload: z.object({\n browserId: z.string(),\n tabId: z.number().optional(),\n url: z.string().optional(),\n }),\n});\n\n/**\n * Submit task execution result\n */\nexport const TaskResultSchema = z.object({\n type: z.literal('task:result'),\n payload: z.object({\n taskId: z.string(),\n success: z.boolean(),\n result: TaskResultContentSchema.optional(),\n error: z.string().optional(),\n }),\n});\n\n/**\n * Notify server that extension tab is mapped to pageId\n */\nexport const TabMappedSchema = z.object({\n type: z.literal('tab:mapped'),\n payload: z.object({\n pageId: z.string(),\n tabId: z.number(),\n }),\n});\n\n/**\n * Ping to keep connection alive\n */\nexport const PingSchema = z.object({\n type: z.literal('ping'),\n});\n\n/**\n * Heartbeat with session state\n */\nexport const HeartbeatSchema = z.object({\n type: z.literal('heartbeat'),\n payload: z.object({\n sessionId: z.string(),\n tabId: z.number().optional(),\n url: z.string().optional(),\n }),\n});\n\n/**\n * All possible messages from extension to server\n */\nexport const ExtensionMessageSchema = z.discriminatedUnion('type', [\n SessionRegisterSchema,\n TaskResultSchema,\n TabMappedSchema,\n PingSchema,\n HeartbeatSchema,\n]);\n\n// ============================================================================\n// Inferred Types\n// ============================================================================\n\nexport type ContentItem = z.infer<typeof ContentItemSchema>;\nexport type TaskResultContent = z.infer<typeof TaskResultContentSchema>;\nexport type TaskTelemetryContext = z.infer<typeof TaskTelemetryContextSchema>;\n\n// Server messages\nexport type TaskPush = z.infer<typeof TaskPushSchema>;\nexport type PageCreated = z.infer<typeof PageCreatedSchema>;\nexport type PageRemoved = z.infer<typeof PageRemovedSchema>;\nexport type SessionAck = z.infer<typeof SessionAckSchema>;\nexport type Pong = z.infer<typeof PongSchema>;\nexport type ServerError = z.infer<typeof ServerErrorSchema>;\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\n// Extension messages\nexport type SessionRegister = z.infer<typeof SessionRegisterSchema>;\nexport type TaskResult = z.infer<typeof TaskResultSchema>;\nexport type TabMapped = z.infer<typeof TabMappedSchema>;\nexport type Ping = z.infer<typeof PingSchema>;\nexport type Heartbeat = z.infer<typeof HeartbeatSchema>;\nexport type ExtensionMessage = z.infer<typeof ExtensionMessageSchema>;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Parse and validate a server message\n */\nexport function parseServerMessage(data: unknown): ServerMessage {\n return ServerMessageSchema.parse(data);\n}\n\n/**\n * Parse and validate an extension message\n */\nexport function parseExtensionMessage(data: unknown): ExtensionMessage {\n return ExtensionMessageSchema.parse(data);\n}\n\n/**\n * Safely parse a server message, returning null on failure\n */\nexport function safeParseServerMessage(data: unknown): ServerMessage | null {\n const result = ServerMessageSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n\n/**\n * Safely parse an extension message, returning null on failure\n */\nexport function safeParseExtensionMessage(data: unknown): ExtensionMessage | null {\n const result = ExtensionMessageSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n","/**\n * WebSocketHub Service\n *\n * DESIGN PATTERNS:\n * - Pub/Sub pattern for broadcasting messages to connected clients\n * - Observer pattern for event-driven communication\n * - Service Layer pattern for encapsulating WebSocket management\n *\n * CODING STANDARDS:\n * - Use typed messages with Zod validation\n * - Implement proper connection lifecycle management\n * - Handle errors gracefully with logging\n *\n * AVOID:\n * - Unbounded connections (implement limits)\n * - Memory leaks from stale connections\n * - Blocking operations in message handlers\n */\n\nimport 'reflect-metadata/lite';\nimport type { WSContext } from 'hono/ws';\nimport { injectable } from 'inversify';\nimport type {\n ExtensionMessage,\n PageCreated,\n PageRemoved,\n ServerMessage,\n SessionAck,\n TaskPush,\n} from '../protocol/extension-messages.js';\nimport { safeParseExtensionMessage } from '../protocol/extension-messages.js';\n\nexport interface ExtensionConnection {\n id: string;\n ws: WSContext;\n browserId: string;\n sessionId?: string;\n connectedAt: Date;\n lastMessageAt: Date;\n}\n\nexport interface WebSocketHubEvents {\n onSessionRegister?: (connection: ExtensionConnection, message: ExtensionMessage) => void;\n onTaskResult?: (connection: ExtensionConnection, message: ExtensionMessage) => void;\n onTabMapped?: (connection: ExtensionConnection, message: ExtensionMessage) => void;\n onHeartbeat?: (connection: ExtensionConnection, message: ExtensionMessage) => void;\n onDisconnect?: (connection: ExtensionConnection) => void;\n}\n\n@injectable()\nexport class WebSocketHub {\n private connections = new Map<string, ExtensionConnection>();\n private browserConnections = new Map<string, Set<string>>();\n private eventHandlers: WebSocketHubEvents = {};\n private readonly maxConnections = 100;\n\n /**\n * Register event handlers for incoming messages\n */\n setEventHandlers(handlers: WebSocketHubEvents): void {\n this.eventHandlers = handlers;\n }\n\n /**\n * Add a new WebSocket connection\n */\n addConnection(ws: WSContext, browserId: string): string {\n if (this.connections.size >= this.maxConnections) {\n throw new Error(`Maximum connections (${this.maxConnections}) reached`);\n }\n\n const connectionId = crypto.randomUUID();\n const connection: ExtensionConnection = {\n id: connectionId,\n ws,\n browserId,\n connectedAt: new Date(),\n lastMessageAt: new Date(),\n };\n\n this.connections.set(connectionId, connection);\n\n // Track by browserId\n if (!this.browserConnections.has(browserId)) {\n this.browserConnections.set(browserId, new Set());\n }\n this.browserConnections.get(browserId)!.add(connectionId);\n\n console.log(`[WebSocketHub] Connection added: ${connectionId} for browser ${browserId}`);\n return connectionId;\n }\n\n /**\n * Remove a WebSocket connection\n */\n removeConnection(connectionId: string): void {\n const connection = this.connections.get(connectionId);\n if (!connection) {\n return;\n }\n\n // Notify disconnect handler\n this.eventHandlers.onDisconnect?.(connection);\n\n // Remove from browser tracking\n const browserConns = this.browserConnections.get(connection.browserId);\n if (browserConns) {\n browserConns.delete(connectionId);\n if (browserConns.size === 0) {\n this.browserConnections.delete(connection.browserId);\n }\n }\n\n this.connections.delete(connectionId);\n console.log(`[WebSocketHub] Connection removed: ${connectionId}`);\n }\n\n /**\n * Get a connection by ID\n */\n getConnection(connectionId: string): ExtensionConnection | undefined {\n return this.connections.get(connectionId);\n }\n\n /**\n * Get all connections for a browser\n */\n getConnectionsByBrowser(browserId: string): ExtensionConnection[] {\n const connectionIds = this.browserConnections.get(browserId);\n if (!connectionIds) {\n return [];\n }\n\n return Array.from(connectionIds)\n .map((id) => this.connections.get(id))\n .filter((conn): conn is ExtensionConnection => conn !== undefined);\n }\n\n /**\n * Check if a browser has an active WebSocket connection\n */\n hasConnection(browserId: string): boolean {\n return this.browserConnections.has(browserId) && (this.browserConnections.get(browserId)?.size ?? 0) > 0;\n }\n\n /**\n * Reassign a connection to a different browserId.\n * Used when an extension connects and needs to be mapped to an existing browser.\n */\n reassignConnection(connectionId: string, newBrowserId: string): boolean {\n const connection = this.connections.get(connectionId);\n if (!connection) {\n return false;\n }\n\n const oldBrowserId = connection.browserId;\n if (oldBrowserId === newBrowserId) {\n return true;\n }\n\n // Remove from old browser tracking\n const oldBrowserConns = this.browserConnections.get(oldBrowserId);\n if (oldBrowserConns) {\n oldBrowserConns.delete(connectionId);\n if (oldBrowserConns.size === 0) {\n this.browserConnections.delete(oldBrowserId);\n }\n }\n\n // Add to new browser tracking\n if (!this.browserConnections.has(newBrowserId)) {\n this.browserConnections.set(newBrowserId, new Set());\n }\n this.browserConnections.get(newBrowserId)!.add(connectionId);\n\n // Update connection's browserId\n connection.browserId = newBrowserId;\n\n return true;\n }\n\n /**\n * Handle incoming message from extension\n */\n handleMessage(connectionId: string, rawData: string | ArrayBuffer): void {\n const connection = this.connections.get(connectionId);\n if (!connection) {\n console.warn(`[WebSocketHub] Message from unknown connection: ${connectionId}`);\n return;\n }\n\n connection.lastMessageAt = new Date();\n\n try {\n const data = typeof rawData === 'string' ? rawData : new TextDecoder().decode(rawData);\n const parsed = JSON.parse(data);\n const message = safeParseExtensionMessage(parsed);\n\n if (!message) {\n console.warn(`[WebSocketHub] Invalid message format from ${connectionId}:`, parsed);\n this.sendError(connection, 'Invalid message format');\n return;\n }\n\n this.routeMessage(connection, message);\n } catch (error) {\n console.error(`[WebSocketHub] Error parsing message from ${connectionId}:`, error);\n this.sendError(connection, 'Failed to parse message');\n }\n }\n\n /**\n * Route message to appropriate handler\n */\n private routeMessage(connection: ExtensionConnection, message: ExtensionMessage): void {\n switch (message.type) {\n case 'session:register':\n this.eventHandlers.onSessionRegister?.(connection, message);\n break;\n case 'task:result':\n this.eventHandlers.onTaskResult?.(connection, message);\n break;\n case 'tab:mapped':\n this.eventHandlers.onTabMapped?.(connection, message);\n break;\n case 'heartbeat':\n this.eventHandlers.onHeartbeat?.(connection, message);\n break;\n case 'ping':\n this.sendToConnection(connection, { type: 'pong' });\n break;\n }\n }\n\n /**\n * Send a message to a specific connection\n */\n sendToConnection(connection: ExtensionConnection, message: ServerMessage): boolean {\n try {\n connection.ws.send(JSON.stringify(message));\n return true;\n } catch (error) {\n console.error(`[WebSocketHub] Failed to send to ${connection.id}:`, error);\n return false;\n }\n }\n\n /**\n * Send error message to connection\n */\n sendError(connection: ExtensionConnection, message: string, code?: string): void {\n this.sendToConnection(connection, {\n type: 'error',\n payload: { message, code },\n });\n }\n\n /**\n * Push a task to a specific browser's extension\n */\n pushTask(browserId: string, task: TaskPush): boolean {\n const connections = this.getConnectionsByBrowser(browserId);\n if (connections.length === 0) {\n console.warn(`[WebSocketHub] No connections for browser ${browserId}`);\n return false;\n }\n\n // Send to first active connection for this browser\n const connection = connections[0];\n return this.sendToConnection(connection, task);\n }\n\n /**\n * Broadcast page created event to all connections for a browser\n */\n broadcastPageCreated(browserId: string, pageId: string, url?: string): void {\n const message: PageCreated = {\n type: 'page:created',\n payload: { pageId, browserId, url },\n };\n\n const connections = this.getConnectionsByBrowser(browserId);\n for (const connection of connections) {\n this.sendToConnection(connection, message);\n }\n }\n\n /**\n * Broadcast page removed event to all connections for a browser\n */\n broadcastPageRemoved(browserId: string, pageId: string): void {\n const message: PageRemoved = {\n type: 'page:removed',\n payload: { pageId },\n };\n\n const connections = this.getConnectionsByBrowser(browserId);\n for (const connection of connections) {\n this.sendToConnection(connection, message);\n }\n }\n\n /**\n * Send session acknowledgment\n */\n sendSessionAck(connection: ExtensionConnection, messageId: string, sessionId: string, controlMode: string): void {\n connection.sessionId = sessionId;\n\n const message: SessionAck = {\n type: 'session:ack',\n id: messageId,\n payload: { sessionId, controlMode },\n };\n\n this.sendToConnection(connection, message);\n }\n\n /**\n * Get connection statistics\n */\n getStats(): {\n totalConnections: number;\n browserCount: number;\n connections: Array<{ id: string; browserId: string; connectedAt: Date }>;\n } {\n return {\n totalConnections: this.connections.size,\n browserCount: this.browserConnections.size,\n connections: Array.from(this.connections.values()).map((c) => ({\n id: c.id,\n browserId: c.browserId,\n connectedAt: c.connectedAt,\n })),\n };\n }\n\n /**\n * Close all connections\n */\n closeAll(): void {\n for (const [connectionId, connection] of this.connections) {\n try {\n connection.ws.close();\n } catch {\n // Ignore close errors\n }\n this.removeConnection(connectionId);\n }\n }\n}\n","/**\n * BaseTool - Abstract base class for MCP browser automation tools\n *\n * DESIGN PATTERNS:\n * - Template Method pattern for consistent tool interface\n * - Dependency Injection via InversifyJS for services\n * - Browser-based page resolution with pageId override\n * - Mode-aware execution (playwright vs extension)\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Return CallToolResult with content array\n * - Handle errors gracefully with isError flag\n * - Tools use browserId + optional pageId for page targeting\n *\n * AVOID:\n * - Global \"active page\" concept\n * - Business logic in base class (delegate to services)\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport type { Page } from 'playwright';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\n/**\n * Common input properties for page targeting.\n * All tools that operate on pages should extend this interface.\n */\nexport interface PageTargetInput {\n /** Page ID to operate on (browserId can be derived from this) */\n pageId: string;\n}\n\n/**\n * Abstract base class for all MCP browser automation tools.\n * Provides browser-based page resolution and response helpers.\n */\n@injectable()\nexport abstract class BaseTool<TInput = unknown> implements Tool<TInput> {\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) protected readonly pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) protected readonly browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) protected readonly toolExecutor: ToolExecutor,\n ) {}\n\n /**\n * Returns the tool definition including name, description, and input schema.\n * Must be implemented by subclasses.\n */\n abstract getDefinition(): ToolDefinition;\n\n /**\n * Executes the tool with the provided input.\n * Must be implemented by subclasses.\n */\n abstract execute(input: TInput): Promise<CallToolResult>;\n\n /**\n * Gets a page by its ID from the registry.\n * @param pageId - The ID of the page to retrieve.\n * @returns The page entry or undefined if not found.\n */\n protected getPage(pageId: string): PageEntry | undefined {\n return this.pageRegistry.get(pageId);\n }\n\n /**\n * Resolves a page by its ID.\n * @param pageId - The page ID.\n * @returns The resolved Page instance.\n * @throws Error if page cannot be resolved or is in extension mode.\n */\n protected resolvePage(pageId: string): Page {\n const entry = this.pageRegistry.get(pageId);\n if (!entry) {\n throw new Error(`Page \"${pageId}\" not found`);\n }\n if (!entry.page) {\n throw new Error(`Page \"${pageId}\" is in extension mode and has no Playwright page`);\n }\n return entry.page;\n }\n\n /**\n * Resolves a page entry by its ID.\n * @param pageId - The page ID.\n * @returns The resolved PageEntry.\n * @throws Error if page cannot be resolved.\n */\n protected resolvePageEntry(pageId: string): PageEntry {\n const entry = this.pageRegistry.get(pageId);\n if (!entry) {\n throw new Error(`Page \"${pageId}\" not found`);\n }\n return entry;\n }\n\n /**\n * Creates a success response with text content.\n * @param text - The success message or content.\n * @returns A CallToolResult with the content.\n */\n protected success(text: string): CallToolResult {\n return {\n content: [{ type: 'text', text }],\n };\n }\n\n /**\n * Creates a success response with JSON content.\n * @param data - The data to serialize as JSON.\n * @returns A CallToolResult with the JSON content.\n */\n protected successJson<T>(data: T): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],\n };\n }\n\n /**\n * Creates a success response with an image.\n * @param base64Data - The base64-encoded image data.\n * @param mimeType - The MIME type of the image (default: image/png).\n * @returns A CallToolResult with the image content.\n */\n protected successImage(base64Data: string, mimeType = 'image/png'): CallToolResult {\n return {\n content: [{ type: 'image', data: base64Data, mimeType }],\n };\n }\n\n /**\n * Creates an error response.\n * @param message - The error message.\n * @returns A CallToolResult with isError flag set.\n */\n protected error(message: string): CallToolResult {\n return {\n content: [{ type: 'text', text: message }],\n isError: true,\n };\n }\n\n /**\n * Wraps execution with standard error handling.\n * @param fn - The async function to execute.\n * @returns The result of the function or an error response.\n */\n protected async safeExecute(fn: () => Promise<CallToolResult>): Promise<CallToolResult> {\n try {\n return await fn();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.error(message);\n }\n }\n\n /**\n * Executes a tool with mode-aware routing.\n * If the page is in extension mode, delegates to the Chrome extension.\n * Otherwise, executes the provided Playwright function.\n *\n * @param toolName - The name of the tool being executed\n * @param pageId - The page ID to operate on\n * @param args - Additional arguments for the tool\n * @param playwrightExecutor - Function to execute for Playwright mode\n * @returns The tool execution result\n */\n protected async executeWithMode(\n toolName: string,\n pageId: string,\n args: Record<string, unknown>,\n playwrightExecutor: () => Promise<CallToolResult>,\n ): Promise<CallToolResult> {\n return this.toolExecutor.execute(toolName, pageId, args, playwrightExecutor);\n }\n}\n","/**\n * ClickTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate element location to ElementLocatorService\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ClickToolInput extends ElementSelector, PageTargetInput {\n /** Number of clicks (default: 1) */\n clickCount?: number;\n /** Mouse button to use */\n button?: 'left' | 'middle' | 'right';\n /** Modifier keys to hold during click */\n modifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>;\n /** Time to wait between mousedown and mouseup in ms */\n delay?: number;\n /** Position offset within the element */\n position?: { x: number; y: number };\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class ClickTool extends BaseTool<ClickToolInput> {\n static readonly TOOL_NAME = 'browser_click';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ClickTool.TOOL_NAME,\n description:\n 'Clicks on an element on the page. Supports CSS selector, XPath, text content, or accessibility snapshot UID for element identification.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n selector: {\n type: 'string',\n description: 'CSS selector (e.g., \"button.submit\", \"#login-btn\")',\n },\n xpath: {\n type: 'string',\n description: 'XPath expression (e.g., \"//button[@type=\\'submit\\']\")',\n },\n text: {\n type: 'string',\n description: 'Text content to match',\n },\n uid: {\n type: 'string',\n description: 'Accessibility snapshot UID reference',\n },\n frame: {\n type: 'string',\n description: 'Frame selector for iframe content',\n },\n clickCount: {\n type: 'number',\n description: 'Number of clicks (default: 1, use 2 for double-click)',\n default: 1,\n },\n button: {\n type: 'string',\n enum: ['left', 'middle', 'right'],\n description: 'Mouse button to use',\n default: 'left',\n },\n modifiers: {\n type: 'array',\n items: { type: 'string', enum: ['Alt', 'Control', 'Meta', 'Shift'] },\n description: 'Modifier keys to hold during click',\n },\n delay: {\n type: 'number',\n description: 'Time to wait between mousedown and mouseup in ms',\n },\n position: {\n type: 'object',\n properties: {\n x: { type: 'number' },\n y: { type: 'number' },\n },\n description: 'Position offset within the element',\n },\n timeout: {\n type: 'number',\n description: `Timeout in milliseconds (default: ${DEFAULT_TOOL_TIMEOUT_MS})`,\n default: DEFAULT_TOOL_TIMEOUT_MS,\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ClickToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n ClickTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n const locator = await this.elementLocator.locate(page, {\n selector: input.selector,\n xpath: input.xpath,\n text: input.text,\n uid: input.uid,\n frame: input.frame,\n });\n\n await locator.click({\n clickCount: input.clickCount,\n button: input.button,\n modifiers: input.modifiers,\n delay: input.delay,\n position: input.position,\n timeout: input.timeout,\n });\n\n return this.success('Clicked element successfully');\n },\n );\n }\n}\n","/**\n * CloseBrowserTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to BrowserService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface CloseBrowserToolInput {\n /** Browser ID to close */\n browserId: string;\n}\n\n@injectable()\nexport class CloseBrowserTool extends BaseTool<CloseBrowserToolInput> {\n static readonly TOOL_NAME = 'browser_close';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: CloseBrowserTool.TOOL_NAME,\n description:\n 'Closes a browser instance by its ID. Terminates the browser process, cleans up all associated pages, and releases resources. Works for all modes: playwright, extension, vm (Docker), and stealth.',\n inputSchema: {\n type: 'object',\n properties: {\n browserId: {\n type: 'string',\n description: 'The browser ID to close (returned by browser_launch)',\n },\n },\n required: ['browserId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: CloseBrowserToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const browser = this.browserService.getBrowser(input.browserId);\n if (!browser) {\n return this.error(`Browser \"${input.browserId}\" not found`);\n }\n\n const pageCount = browser.pageIds.size;\n const mode = browser.mode ?? 'playwright';\n\n await this.browserService.closeBrowser(input.browserId);\n\n return this.successJson({\n closedBrowserId: input.browserId,\n mode,\n pagesRemoved: pageCount,\n message: `Browser \"${input.browserId}\" closed successfully`,\n });\n });\n }\n}\n","/**\n * ClosePageTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to PageRegistry and SessionService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { IProfileService } from '../services/ProfileService.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface ClosePageToolInput {\n /** Page ID to close */\n pageId: string;\n}\n\n@injectable()\nexport class ClosePageTool extends BaseTool<ClosePageToolInput> {\n static readonly TOOL_NAME = 'browser_close_page';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ProfileService) private profileService: IProfileService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ClosePageTool.TOOL_NAME,\n description:\n 'Closes a page (tab) by its ID. Removes the page from the session and registry. If closed page was current, another page becomes current.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Page ID to close',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ClosePageToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.pageRegistry.get(input.pageId);\n if (!pageEntry) {\n return this.error(`Page \"${input.pageId}\" not found`);\n }\n\n const { browserId, profileName, mode } = pageEntry;\n const browser = this.browserService.getBrowser(browserId);\n if (!browser) {\n return this.error(`Browser \"${browserId}\" not found`);\n }\n\n const wasCurrentPage = browser.currentPageId === input.pageId;\n const isLastPage = browser.pageIds.size === 1;\n\n if (mode === 'extension' || mode === 'vm') {\n const result = await this.executeWithMode(\n ClosePageTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => this.error(`Page \"${input.pageId}\" is not in extension mode`),\n );\n\n if (result.isError) {\n return result;\n }\n\n await this.browserService.finalizeExtensionRecording(browserId, input.pageId);\n\n this.pageRegistry.remove(input.pageId);\n browser.pageIds.delete(input.pageId);\n if (browser.currentPageId === input.pageId) {\n browser.currentPageId = Array.from(browser.pageIds)[0] ?? null;\n }\n\n if (isLastPage) {\n try {\n await this.browserService.closeBrowser(browserId);\n } catch (error) {\n console.error(`Failed to close extension browser \"${browserId}\" after last tab shutdown:`, error);\n }\n }\n\n return this.successJson({\n closedPageId: input.pageId,\n browserId,\n wasCurrentPage,\n newCurrentPageId: browser.currentPageId,\n browserKeptOpen: !isLastPage,\n message: isLastPage\n ? `Page \"${input.pageId}\" closed. Browser \"${browserId}\" was also closed because no tabs remained.`\n : `Page \"${input.pageId}\" closed successfully`,\n });\n }\n\n if (!pageEntry.page) {\n return this.error(`Page \"${input.pageId}\" is in extension mode and cannot be closed via Playwright`);\n }\n await pageEntry.page.close();\n\n const updatedBrowser = this.browserService.getBrowser(browserId);\n const newCurrentPageId = updatedBrowser?.currentPageId ?? null;\n\n // Auto-save profile state when last page is closed\n if (isLastPage) {\n if (profileName && browser.context) {\n try {\n const storageState = await browser.context.storageState();\n await this.profileService.saveStorageState(profileName, storageState as Record<string, unknown>);\n } catch (error) {\n console.error(`Failed to auto-save profile \"${profileName}\":`, error);\n }\n }\n\n // Only close browser if it's NOT a profile-based browser\n // Profile-based browsers stay open for reuse (smart browser management)\n if (!profileName) {\n try {\n await this.browserService.closeBrowser(browserId);\n } catch (error) {\n console.error(`Failed to auto-close browser \"${browserId}\":`, error);\n }\n }\n }\n\n return this.successJson({\n closedPageId: input.pageId,\n browserId,\n wasCurrentPage,\n newCurrentPageId,\n browserKeptOpen: isLastPage && !!profileName,\n message:\n isLastPage && profileName\n ? `Page \"${input.pageId}\" closed. Browser kept open for profile \"${profileName}\" reuse.`\n : `Page \"${input.pageId}\" closed successfully`,\n });\n });\n }\n}\n","import 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserProfile, IProfileService } from '../services/ProfileService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\nexport interface CreateProfileToolInput {\n name: string;\n browserType?: BrowserProfile['browserType'];\n viewport?: BrowserProfile['viewport'];\n userAgent?: string;\n locale?: string;\n timezone?: string;\n colorScheme?: BrowserProfile['colorScheme'];\n}\n\n@injectable()\nexport class CreateProfileTool implements Tool<CreateProfileToolInput> {\n static readonly TOOL_NAME = 'browser_create_profile';\n\n constructor(@inject(PLAYWRIGHT_TYPES.ProfileService) private readonly profileService: IProfileService) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: CreateProfileTool.TOOL_NAME,\n description: 'Creates a browser profile for persistent browser settings and session reuse.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'Unique profile name' },\n browserType: {\n type: 'string',\n enum: ['chromium', 'firefox', 'webkit'],\n description: 'Preferred browser type for the profile',\n default: 'chromium',\n },\n viewport: {\n type: 'object',\n properties: {\n width: { type: 'number' },\n height: { type: 'number' },\n },\n required: ['width', 'height'],\n additionalProperties: false,\n },\n userAgent: { type: 'string' },\n locale: { type: 'string' },\n timezone: { type: 'string' },\n colorScheme: {\n type: 'string',\n enum: ['light', 'dark', 'no-preference'],\n },\n },\n required: ['name'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: CreateProfileToolInput): Promise<CallToolResult> {\n try {\n const profile = await this.profileService.create({\n name: input.name,\n browserType: input.browserType ?? 'chromium',\n viewport: input.viewport,\n userAgent: input.userAgent,\n locale: input.locale,\n timezone: input.timezone,\n colorScheme: input.colorScheme,\n });\n\n return {\n content: [{ type: 'text', text: JSON.stringify(profile, null, 2) }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n };\n }\n }\n}\n","/**\n * DeleteProfileTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with try/catch\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IProfileService } from '../services/ProfileService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\nexport interface DeleteProfileToolInput {\n /** Name of the profile to delete */\n name: string;\n}\n\n@injectable()\nexport class DeleteProfileTool implements Tool<DeleteProfileToolInput> {\n static readonly TOOL_NAME = 'browser_delete_profile';\n\n constructor(@inject(PLAYWRIGHT_TYPES.ProfileService) private readonly profileService: IProfileService) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: DeleteProfileTool.TOOL_NAME,\n description: 'Deletes a browser profile and all its associated storage state data.',\n inputSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'Name of the profile to delete',\n },\n },\n required: ['name'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: DeleteProfileToolInput): Promise<CallToolResult> {\n try {\n await this.profileService.delete(input.name);\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n message: `Profile \"${input.name}\" deleted successfully`,\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * DiscoverSpecsTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'discover_specs')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to SpecDiscoveryService\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport { isAbsolute } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ISpecDiscoveryService } from '../services/SpecDiscoveryService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\ninterface DiscoverSpecsToolInput {\n /** Absolute path to search for e2e projects */\n basePath: string;\n /** Filter by project name (e.g., 'boomlink-e2e') */\n projectName?: string;\n /** Filter specs by name pattern (supports * and ? wildcards) */\n specPattern?: string;\n /** Include metadata detection like hasArgsSchema */\n includeMetadata?: boolean;\n}\n\n@injectable()\nexport class DiscoverSpecsTool implements Tool<DiscoverSpecsToolInput> {\n static readonly TOOL_NAME = 'discover_specs';\n\n constructor(@inject(PLAYWRIGHT_TYPES.SpecDiscoveryService) private specDiscoveryService: ISpecDiscoveryService) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: DiscoverSpecsTool.TOOL_NAME,\n description:\n 'Discovers e2e projects (directories with playwright.config.ts) and their spec files. Use to find available specs before running them.',\n inputSchema: {\n type: 'object',\n properties: {\n basePath: {\n type: 'string',\n description: 'Absolute path to search for e2e projects (required)',\n },\n projectName: {\n type: 'string',\n description: \"Filter by project name (e.g., 'boomlink-e2e')\",\n },\n specPattern: {\n type: 'string',\n description: 'Filter specs by name pattern (supports * and ? wildcards, e.g., \"auth*\", \"*.spec.ts\")',\n },\n includeMetadata: {\n type: 'boolean',\n description: 'Include metadata like hasArgsSchema detection (default: true)',\n default: true,\n },\n },\n required: ['basePath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: DiscoverSpecsToolInput): Promise<CallToolResult> {\n if (!input.basePath || !isAbsolute(input.basePath)) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: basePath must be an absolute path (e.g., /Users/me/project)',\n },\n ],\n isError: true,\n };\n }\n\n try {\n const basePath = input.basePath;\n const includeMetadata = input.includeMetadata !== false;\n\n // If filtering by project or pattern, use filterSpecs\n if (input.projectName || input.specPattern) {\n const specs = await this.specDiscoveryService.filterSpecs(basePath, {\n projectName: input.projectName,\n specPattern: input.specPattern,\n });\n\n // Format output for filtered specs\n const output = {\n filter: {\n basePath,\n projectName: input.projectName,\n specPattern: input.specPattern,\n },\n totalSpecs: specs.length,\n specs: specs.map((spec) => ({\n name: spec.name,\n path: spec.path,\n relativePath: spec.relativePath,\n ...(includeMetadata && { hasArgsSchema: spec.hasArgsSchema }),\n })),\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n }\n\n // Otherwise, discover all projects\n const projects = await this.specDiscoveryService.discoverProjects(basePath);\n\n // Format output for project discovery\n const output = {\n basePath,\n totalProjects: projects.length,\n totalSpecs: projects.reduce((sum, p) => sum + p.specs.length, 0),\n projects: projects.map((project) => ({\n name: project.name,\n path: project.path,\n configPath: project.configPath,\n testDir: project.testDir,\n specCount: project.specs.length,\n specs: project.specs.map((spec) => ({\n name: spec.name,\n path: spec.path,\n relativePath: spec.relativePath,\n ...(includeMetadata && { hasArgsSchema: spec.hasArgsSchema }),\n })),\n })),\n };\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error discovering specs: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * DragTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n * - Session-based page resolution with optional pageId override\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface DragToolInput extends PageTargetInput {\n /** Source element selector options */\n source: ElementSelector;\n /** Target element selector options or coordinates */\n target: ElementSelector | { x: number; y: number };\n /** Whether to bypass actionability checks */\n force?: boolean;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class DragTool extends BaseTool<DragToolInput> {\n static readonly TOOL_NAME = 'browser_drag';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: DragTool.TOOL_NAME,\n description: 'Drags an element to a target location or element.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n source: {\n type: 'object',\n description: 'Source element selector',\n properties: {\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n },\n },\n target: {\n type: 'object',\n description: 'Target element selector or coordinates',\n properties: {\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n x: { type: 'number', description: 'X coordinate' },\n y: { type: 'number', description: 'Y coordinate' },\n },\n },\n force: { type: 'boolean', description: 'Bypass actionability checks', default: false },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId', 'source', 'target'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: DragToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n DragTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const sourceLocator = await this.elementLocator.locate(page, input.source);\n\n if ('x' in input.target && 'y' in input.target) {\n await sourceLocator.dragTo(page.locator('body'), {\n targetPosition: { x: input.target.x, y: input.target.y },\n force: input.force,\n timeout: input.timeout,\n });\n } else {\n const targetLocator = await this.elementLocator.locate(page, input.target as ElementSelector);\n await sourceLocator.dragTo(targetLocator, {\n force: input.force,\n timeout: input.timeout,\n });\n }\n\n return this.success('Dragged element successfully');\n },\n );\n }\n}\n","/**\n * EmulateTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface EmulateToolInput extends PageTargetInput {\n /** Geolocation coordinates to emulate */\n geolocation?: {\n latitude: number;\n longitude: number;\n accuracy?: number;\n };\n /** Locale to emulate (e.g., 'en-US', 'de-DE') */\n locale?: string;\n /** Timezone to emulate (e.g., 'America/New_York') */\n timezone?: string;\n /** User agent string to use */\n userAgent?: string;\n /** Whether to emulate offline mode */\n offline?: boolean;\n /** Color scheme preference */\n colorScheme?: 'light' | 'dark' | 'no-preference';\n /** Reduced motion preference */\n reducedMotion?: 'reduce' | 'no-preference';\n}\n\n@injectable()\nexport class EmulateTool extends BaseTool<EmulateToolInput> {\n static readonly TOOL_NAME = 'browser_emulate';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: EmulateTool.TOOL_NAME,\n description:\n 'Emulates device characteristics like geolocation, timezone, locale, color scheme, and network conditions.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n geolocation: {\n type: 'object',\n description: 'Geolocation coordinates to emulate',\n properties: {\n latitude: { type: 'number', minimum: -90, maximum: 90 },\n longitude: { type: 'number', minimum: -180, maximum: 180 },\n accuracy: { type: 'number', minimum: 0 },\n },\n required: ['latitude', 'longitude'],\n },\n locale: {\n type: 'string',\n description: 'Locale to emulate (e.g., \"en-US\", \"de-DE\")',\n },\n timezone: {\n type: 'string',\n description: 'Timezone to emulate (e.g., \"America/New_York\", \"Europe/London\")',\n },\n userAgent: {\n type: 'string',\n description: 'User agent string to use',\n },\n offline: {\n type: 'boolean',\n description: 'Whether to emulate offline mode',\n },\n colorScheme: {\n type: 'string',\n enum: ['light', 'dark', 'no-preference'],\n description: 'Color scheme preference',\n },\n reducedMotion: {\n type: 'string',\n enum: ['reduce', 'no-preference'],\n description: 'Reduced motion preference',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: EmulateToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n EmulateTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () =>\n this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n const context = pageEntry.context;\n const page = pageEntry.page;\n\n if (!context || !page) {\n return this.error(`Page \"${input.pageId}\" is in extension mode and cannot be emulated via Playwright`);\n }\n\n const appliedSettings: Record<string, unknown> = {};\n\n if (input.geolocation) {\n await context.setGeolocation(input.geolocation);\n appliedSettings.geolocation = input.geolocation;\n }\n\n if (input.offline !== undefined) {\n await context.setOffline(input.offline);\n appliedSettings.offline = input.offline;\n }\n\n if (input.colorScheme) {\n await page.emulateMedia({ colorScheme: input.colorScheme });\n appliedSettings.colorScheme = input.colorScheme;\n }\n\n if (input.reducedMotion) {\n await page.emulateMedia({ reducedMotion: input.reducedMotion });\n appliedSettings.reducedMotion = input.reducedMotion;\n }\n\n if (input.locale) {\n appliedSettings.locale = input.locale;\n appliedSettings.localeNote = 'Locale changes may require a new browser context to take full effect';\n }\n\n if (input.timezone) {\n appliedSettings.timezone = input.timezone;\n appliedSettings.timezoneNote = 'Timezone changes may require a new browser context to take full effect';\n }\n\n if (input.userAgent) {\n appliedSettings.userAgent = input.userAgent;\n appliedSettings.userAgentNote = 'User agent changes may require a new browser context to take full effect';\n }\n\n return this.successJson({\n success: true,\n pageId: pageEntry.id,\n appliedSettings,\n });\n }),\n );\n }\n}\n","/**\n * EvaluateScriptTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface EvaluateScriptToolInput extends PageTargetInput {\n /** JavaScript code to execute in the page context */\n script: string;\n /** Optional argument to pass to the script function */\n arg?: unknown;\n}\n\n@injectable()\nexport class EvaluateScriptTool extends BaseTool<EvaluateScriptToolInput> {\n static readonly TOOL_NAME = 'browser_evaluate_script';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: EvaluateScriptTool.TOOL_NAME,\n description:\n 'Executes JavaScript code in the page context and returns the result. The script can be a function body or an expression.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n script: {\n type: 'string',\n description:\n 'JavaScript code to execute. Can be a function body or expression (e.g., \"document.title\" or \"() => window.location.href\")',\n },\n arg: {\n description: 'Optional argument to pass to the script if it is a function',\n },\n },\n required: ['pageId', 'script'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: EvaluateScriptToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n EvaluateScriptTool.TOOL_NAME,\n input.pageId,\n { script: input.script, arg: input.arg },\n async () => {\n const page = this.resolvePage(input.pageId);\n\n // Execute the script in the page context\n const result = await page.evaluate(input.script, input.arg);\n\n // Serialize the result for output\n let serializedResult: string;\n try {\n serializedResult = JSON.stringify(result, null, 2);\n } catch {\n serializedResult = String(result);\n }\n\n // Save serialized result to temp file\n const filename = `evaluate-${randomUUID()}.json`;\n const filePath = join(tmpdir(), filename);\n await writeFile(filePath, serializedResult, 'utf-8');\n\n return this.success(`Result saved to: ${filePath}`);\n },\n );\n }\n}\n","/**\n * ExpectTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport { expect } from '../stubs/playwright-test.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\n/** Supported assertion types */\nexport type AssertionType =\n | 'toBeVisible'\n | 'toBeHidden'\n | 'toBeEnabled'\n | 'toBeDisabled'\n | 'toBeChecked'\n | 'toBeEditable'\n | 'toBeFocused'\n | 'toBeEmpty'\n | 'toHaveText'\n | 'toContainText'\n | 'toHaveValue'\n | 'toHaveAttribute'\n | 'toHaveClass'\n | 'toHaveCount'\n | 'toHaveCSS'\n | 'toHaveId'\n | 'toHaveTitle'\n | 'toHaveURL';\n\nexport interface ExpectToolInput extends PageTargetInput {\n /** CSS selector for the element to assert on (not required for page-level assertions like toHaveTitle, toHaveURL) */\n selector?: string;\n /** Type of assertion to perform */\n assertion: AssertionType;\n /** Expected value for assertions that require one (e.g., toHaveText, toHaveValue) */\n expected?: string | number | RegExp;\n /** Attribute name for toHaveAttribute assertion */\n attributeName?: string;\n /** Whether to negate the assertion (not.toBeVisible, etc.) */\n not?: boolean;\n /** Timeout in milliseconds for the assertion (default: 5000) */\n timeout?: number;\n}\n\ntype ExpectationMethod = (...args: unknown[]) => Promise<unknown>;\ntype ExpectationMethods = Record<string, ExpectationMethod>;\n\n@injectable()\nexport class ExpectTool extends BaseTool<ExpectToolInput> {\n static readonly TOOL_NAME = 'browser_expect';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ExtensionPageProxy) private readonly extensionPageProxy: IExtensionPageProxy,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ExpectTool.TOOL_NAME,\n description:\n 'Performs Playwright expect() assertions on page elements. Supports various assertion types like toBeVisible, toHaveText, toHaveValue, toBeEnabled, etc.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n selector: {\n type: 'string',\n description:\n 'CSS selector for the element to assert on (not required for page-level assertions like toHaveTitle, toHaveURL)',\n },\n assertion: {\n type: 'string',\n enum: [\n 'toBeVisible',\n 'toBeHidden',\n 'toBeEnabled',\n 'toBeDisabled',\n 'toBeChecked',\n 'toBeEditable',\n 'toBeFocused',\n 'toBeEmpty',\n 'toHaveText',\n 'toContainText',\n 'toHaveValue',\n 'toHaveAttribute',\n 'toHaveClass',\n 'toHaveCount',\n 'toHaveCSS',\n 'toHaveId',\n 'toHaveTitle',\n 'toHaveURL',\n ],\n description: 'Type of assertion to perform',\n },\n expected: {\n oneOf: [{ type: 'string' }, { type: 'number' }],\n description: 'Expected value for assertions that require one (e.g., toHaveText, toHaveValue)',\n },\n attributeName: {\n type: 'string',\n description: 'Attribute name for toHaveAttribute or toHaveCSS assertion',\n },\n not: {\n type: 'boolean',\n description: 'Whether to negate the assertion (not.toBeVisible, etc.)',\n },\n timeout: {\n type: 'number',\n description: 'Timeout in milliseconds for the assertion (default: 5000)',\n },\n },\n required: ['pageId', 'assertion'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ExpectToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n const page =\n pageEntry.mode === 'extension'\n ? (() => {\n this.extensionPageProxy.setTarget(pageEntry.id, pageEntry.browserId);\n return this.extensionPageProxy;\n })()\n : this.resolvePage(input.pageId);\n const { selector, assertion, expected, attributeName, not: negate, timeout = 5000 } = input;\n\n // Page-level assertions that don't require a selector\n const pageLevelAssertions = ['toHaveTitle', 'toHaveURL'];\n\n // Element-level assertions require a selector\n if (!pageLevelAssertions.includes(assertion) && !selector) {\n return this.error(`Selector is required for assertion type: ${assertion}`);\n }\n\n try {\n if (pageLevelAssertions.includes(assertion)) {\n // Page-level assertion\n const expectation = (negate ? expect(page).not : expect(page)) as unknown as ExpectationMethods;\n\n if (assertion === 'toHaveTitle') {\n await expectation.toHaveTitle(expected as string | RegExp, { timeout });\n } else if (assertion === 'toHaveURL') {\n await expectation.toHaveURL(expected as string | RegExp, { timeout });\n }\n } else {\n // Element-level assertion\n const locator = page.locator(selector as string);\n const expectation = (negate ? expect(locator).not : expect(locator)) as unknown as ExpectationMethods;\n\n switch (assertion) {\n case 'toBeVisible':\n await expectation.toBeVisible({ timeout });\n break;\n case 'toBeHidden':\n await expectation.toBeHidden({ timeout });\n break;\n case 'toBeEnabled':\n await expectation.toBeEnabled({ timeout });\n break;\n case 'toBeDisabled':\n await expectation.toBeDisabled({ timeout });\n break;\n case 'toBeChecked':\n await expectation.toBeChecked({ timeout });\n break;\n case 'toBeEditable':\n await expectation.toBeEditable({ timeout });\n break;\n case 'toBeFocused':\n await expectation.toBeFocused({ timeout });\n break;\n case 'toBeEmpty':\n await expectation.toBeEmpty({ timeout });\n break;\n case 'toHaveText':\n await expectation.toHaveText(expected as string | RegExp, { timeout });\n break;\n case 'toContainText':\n await expectation.toContainText(expected as string | RegExp, { timeout });\n break;\n case 'toHaveValue':\n await expectation.toHaveValue(expected as string | RegExp, { timeout });\n break;\n case 'toHaveAttribute':\n if (!attributeName) {\n return this.error('attributeName is required for toHaveAttribute assertion');\n }\n await expectation.toHaveAttribute(attributeName, expected as string | RegExp, { timeout });\n break;\n case 'toHaveClass':\n await expectation.toHaveClass(expected as string | RegExp, { timeout });\n break;\n case 'toHaveCount':\n await expectation.toHaveCount(expected as number, { timeout });\n break;\n case 'toHaveCSS':\n if (!attributeName) {\n return this.error('attributeName (CSS property name) is required for toHaveCSS assertion');\n }\n await expectation.toHaveCSS(attributeName, expected as string | RegExp, { timeout });\n break;\n case 'toHaveId':\n await expectation.toHaveId(expected as string | RegExp, { timeout });\n break;\n default:\n return this.error(`Unsupported assertion type: ${assertion}`);\n }\n }\n\n return this.successJson({\n success: true,\n assertion: negate ? `not.${assertion}` : assertion,\n selector: selector ?? null,\n passed: true,\n });\n } catch (error) {\n // Assertion failed - this is expected behavior, not an error\n const errorMessage = error instanceof Error ? error.message : 'Assertion failed';\n return this.successJson({\n success: true,\n assertion: negate ? `not.${assertion}` : assertion,\n selector: selector ?? null,\n passed: false,\n failureMessage: errorMessage,\n });\n }\n });\n }\n}\n","/**\n * FillTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface FillToolInput extends ElementSelector, PageTargetInput {\n /** The text value to fill into the input */\n value: string;\n /** Whether to bypass actionability checks (default: false) */\n force?: boolean;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class FillTool extends BaseTool<FillToolInput> {\n static readonly TOOL_NAME = 'browser_fill';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: FillTool.TOOL_NAME,\n description:\n 'Fills an input field with text, clearing any existing content first. Use for form inputs, textareas, and contenteditable elements.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n value: { type: 'string', description: 'The text value to fill into the input' },\n force: { type: 'boolean', description: 'Bypass actionability checks', default: false },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId', 'value'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: FillToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n FillTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const locator = await this.elementLocator.locate(page, input);\n await locator.fill(input.value, { force: input.force, timeout: input.timeout });\n return this.success(`Filled element with: \"${input.value}\"`);\n },\n );\n }\n}\n","/**\n * GetNetworkRequestTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to PageMonitorService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageMonitorService } from '../services/PageMonitorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface GetNetworkRequestToolInput extends PageTargetInput {\n /** The ID of the network request to retrieve */\n requestId: string;\n}\n\n@injectable()\nexport class GetNetworkRequestTool extends BaseTool<GetNetworkRequestToolInput> {\n static readonly TOOL_NAME = 'browser_get_network_request';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.PageMonitorService) private readonly pageMonitor: IPageMonitorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GetNetworkRequestTool.TOOL_NAME,\n description:\n 'Retrieves detailed information about a specific network request by ID, including headers, body, and response data.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n requestId: {\n type: 'string',\n description: 'The ID of the network request to retrieve (e.g., \"req-1\")',\n },\n },\n required: ['pageId', 'requestId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GetNetworkRequestToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n return this.executeWithMode(\n GetNetworkRequestTool.TOOL_NAME,\n pageEntry.id,\n input as unknown as Record<string, unknown>,\n async () => {\n const request = this.pageMonitor.getNetworkRequest(pageEntry.id, input.requestId);\n\n if (!request) {\n return this.error(`Network request \"${input.requestId}\" not found for page \"${pageEntry.id}\"`);\n }\n\n return this.successJson({\n id: request.id,\n url: request.url,\n method: request.method,\n resourceType: request.resourceType,\n headers: request.headers,\n postData: request.postData,\n timestamp: request.timestamp.toISOString(),\n response: request.response\n ? {\n status: request.response.status,\n statusText: request.response.statusText,\n headers: request.response.headers,\n timing: request.response.timing,\n }\n : null,\n });\n },\n );\n });\n }\n}\n","/**\n * GoBackTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface GoBackToolInput extends PageTargetInput {\n /** Wait until condition: 'load', 'domcontentloaded', 'networkidle', or 'commit' */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class GoBackTool extends BaseTool<GoBackToolInput> {\n static readonly TOOL_NAME = 'browser_go_back';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GoBackTool.TOOL_NAME,\n description: 'Navigates the browser back in history.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n waitUntil: {\n type: 'string',\n enum: ['load', 'domcontentloaded', 'networkidle', 'commit'],\n description: 'Wait until condition',\n default: 'load',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GoBackToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n GoBackTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const response = await page.goBack({\n waitUntil: input.waitUntil,\n timeout: input.timeout,\n });\n\n if (response === null) {\n return this.success('No previous page in history');\n }\n\n const status = response.status();\n return this.success(`Navigated back (status: ${status})`);\n },\n );\n }\n}\n","/**\n * GoForwardTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface GoForwardToolInput extends PageTargetInput {\n /** Wait until condition: 'load', 'domcontentloaded', 'networkidle', or 'commit' */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class GoForwardTool extends BaseTool<GoForwardToolInput> {\n static readonly TOOL_NAME = 'browser_go_forward';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: GoForwardTool.TOOL_NAME,\n description: 'Navigates the browser forward in history.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n waitUntil: {\n type: 'string',\n enum: ['load', 'domcontentloaded', 'networkidle', 'commit'],\n description: 'Wait until condition',\n default: 'load',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: GoForwardToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n GoForwardTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const response = await page.goForward({\n waitUntil: input.waitUntil,\n timeout: input.timeout,\n });\n\n if (response === null) {\n return this.success('No next page in history');\n }\n\n const status = response.status();\n return this.success(`Navigated forward (status: ${status})`);\n },\n );\n }\n}\n","/**\n * HandleDialogTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport type { Dialog } from 'playwright';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface HandleDialogToolInput extends PageTargetInput {\n /** Whether to accept or dismiss the dialog */\n accept: boolean;\n /** Text to enter for prompt dialogs */\n promptText?: string;\n /** Timeout in milliseconds to wait for dialog */\n timeout?: number;\n}\n\n@injectable()\nexport class HandleDialogTool extends BaseTool<HandleDialogToolInput> {\n static readonly TOOL_NAME = 'browser_handle_dialog';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: HandleDialogTool.TOOL_NAME,\n description:\n 'Handles JavaScript dialogs (alert, confirm, prompt) by accepting or dismissing them. Set up a listener before triggering the action that opens the dialog.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n accept: {\n type: 'boolean',\n description: 'Whether to accept (true) or dismiss (false) the dialog',\n },\n promptText: {\n type: 'string',\n description: 'Text to enter for prompt dialogs (only used when accept is true)',\n },\n timeout: {\n type: 'number',\n description: `Timeout in milliseconds to wait for dialog (default: ${DEFAULT_TOOL_TIMEOUT_MS})`,\n default: DEFAULT_TOOL_TIMEOUT_MS,\n },\n },\n required: ['pageId', 'accept'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: HandleDialogToolInput): Promise<CallToolResult> {\n return this.safeExecute(() =>\n this.executeWithMode(\n HandleDialogTool.TOOL_NAME,\n input.pageId,\n { accept: input.accept, promptText: input.promptText, timeout: input.timeout },\n async () => {\n const page = this.resolvePage(input.pageId);\n const timeout = input.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;\n const timeoutId = setTimeout(() => {\n page.off('dialog', onDialog);\n }, timeout);\n\n const onDialog = async (dialog: Dialog) => {\n clearTimeout(timeoutId);\n try {\n if (input.accept) {\n await dialog.accept(input.promptText);\n } else {\n await dialog.dismiss();\n }\n } finally {\n page.off('dialog', onDialog);\n }\n };\n\n page.once('dialog', onDialog);\n\n return this.successJson({\n armed: true,\n action: input.accept ? 'accept' : 'dismiss',\n timeout,\n promptText: input.promptText ?? null,\n });\n },\n ),\n );\n }\n}\n","/**\n * HoverTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface HoverToolInput extends ElementSelector, PageTargetInput {\n /** Position offset within the element */\n position?: { x: number; y: number };\n /** Modifier keys to hold during hover */\n modifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>;\n /** Whether to bypass actionability checks */\n force?: boolean;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class HoverTool extends BaseTool<HoverToolInput> {\n static readonly TOOL_NAME = 'browser_hover';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: HoverTool.TOOL_NAME,\n description: 'Hovers over an element to trigger hover states, tooltips, or dropdown menus.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n position: {\n type: 'object',\n properties: { x: { type: 'number' }, y: { type: 'number' } },\n description: 'Position offset within element',\n },\n modifiers: {\n type: 'array',\n items: { type: 'string', enum: ['Alt', 'Control', 'Meta', 'Shift'] },\n description: 'Modifier keys to hold',\n },\n force: { type: 'boolean', description: 'Bypass actionability checks', default: false },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: HoverToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n HoverTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const locator = await this.elementLocator.locate(page, input);\n await locator.hover({\n position: input.position,\n modifiers: input.modifiers,\n force: input.force,\n timeout: input.timeout,\n });\n return this.success('Hovered over element successfully');\n },\n );\n }\n}\n","/**\n * LaunchBrowserTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation to BrowserService\n * - JSON Schema validation for inputs\n * - Mode-aware execution (playwright vs extension)\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { type PortRegistryRecord, PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IStealthLauncher } from '../services/StealthLauncher.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\nimport {\n DEFAULT_MCP_PORT,\n buildPlaywrightBaseUrl,\n getPlaywrightHost,\n getPlaywrightPort,\n} from '../utils/networkConfig.js';\nimport type { ProxyConfig } from '../utils/proxyAuthExtension.js';\n\nconst WORKSPACE_MARKERS = ['pnpm-workspace.yaml', 'nx.json', '.git'];\nconst ABOUT_BLANK_URL = 'about:blank';\nconst EXTENSION_BROWSER_ID_PREFIX = 'browser-extension-';\nconst DEBUG_EXTENSION_RECORDING_STDOUT = process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1';\nconst PLAYWRIGHT_HTTP_SERVICE_NAME = 'browse-tool-http';\nconst PLAYWRIGHT_HTTP_SERVICE_TYPE = 'tool' as const;\nconst DEFAULT_ENVIRONMENT = 'development';\nconst DEFAULT_DOCKER_VM_SERVER_HOST = 'host.docker.internal';\nconst LOG_PREFIX = '[LaunchBrowserTool]';\nconst DOCKER_VM_ALIASES = new Set(['docker', 'docker-vm', 'docker-chromium', 'docker-chrome-testing']);\n\n/**\n * Resolve the nearest workspace root by scanning parent directories for workspace markers.\n */\nfunction resolveWorkspaceRoot(startPath = process.cwd()): string {\n let currentDir = path.resolve(startPath);\n\n while (true) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(path.join(currentDir, marker))) {\n return currentDir;\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return process.cwd();\n }\n currentDir = parentDir;\n }\n}\n\n/** Browser launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\nexport interface LaunchBrowserToolInput {\n /** Execution mode: 'playwright' uses Playwright APIs, 'extension' uses Playwright with extension, 'vm' launches in an isolated runtime (e.g., Docker), 'stealth' spawns Chrome directly without automation flags */\n mode?: LaunchMode;\n /** Browser type to use */\n browserType?: 'chromium' | 'firefox' | 'webkit';\n /** Run browser in headless mode */\n headless?: boolean;\n /** Optional URL to navigate to after launch */\n url?: string;\n /** Optional profile name to use */\n profileName?: string;\n /** Custom command path for vm mode */\n vmCommand?: string;\n /** Custom command arguments for vm mode */\n vmCommandArgs?: string[];\n /** Prefer Chrome for Testing in vm mode */\n vmUseChromeForTesting?: boolean;\n /** Optional browserId to use for vm mode */\n vmBrowserId?: string;\n /** Optional startup delay (ms) before returning in vm mode */\n startupDelayMs?: number;\n /** Explicit MCP server URL reachable from inside VM (overrides registry/env discovery) */\n vmServerUrl?: string;\n /** Explicit extension path that exists inside VM filesystem */\n vmExtensionPath?: string;\n /** Expose Docker VM VNC (5900) and noVNC (7900) ports to host */\n vmEnableVnc?: boolean;\n /** Host port mapped to Docker VNC server (container 5900) */\n vmVncPort?: number;\n /** Host port mapped to Docker noVNC server (container 7900) */\n vmNoVncPort?: number;\n /** Optional VNC password passed to Docker VM runtime (SE_VNC_PASSWORD) */\n vmVncPassword?: string;\n /** Base URL for relative navigation */\n baseURL?: string;\n /** Directory to save video recordings. If provided, video will be recorded for all pages */\n videoDir?: string;\n /** Proxy configuration for routing traffic through a proxy server */\n proxy?: ProxyConfig;\n}\n\n/**\n * Tool for launching a new browser instance.\n * Returns browserId and pageId for subsequent browser operations.\n */\n@injectable()\nexport class LaunchBrowserTool implements Tool<LaunchBrowserToolInput> {\n static readonly TOOL_NAME = 'browser_launch';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.BrowserService) private readonly browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.StealthLauncher) private readonly stealthLauncher: IStealthLauncher,\n ) {}\n\n /**\n * Returns the tool definition including name, description, and input schema.\n * @returns The tool definition for MCP registration.\n */\n getDefinition(): ToolDefinition {\n return {\n name: LaunchBrowserTool.TOOL_NAME,\n description:\n 'Launches a new browser instance. Returns browserId and pageId for subsequent browser operations. Use mode \"extension\" for bot-detection-free automation via Chrome extension, or mode \"vm\" for isolated runtime launch.',\n inputSchema: {\n type: 'object',\n properties: {\n mode: {\n type: 'string',\n enum: ['playwright', 'extension', 'vm', 'stealth'],\n description:\n 'Execution mode. \"playwright\" uses Playwright APIs. \"extension\" uses Playwright with Chrome extension. \"vm\" launches in an isolated runtime (for example via Docker). \"stealth\" spawns Chrome directly without automation flags.',\n default: 'playwright',\n },\n browserType: {\n type: 'string',\n enum: ['chromium', 'firefox', 'webkit'],\n description: 'Browser type to use (only applies to playwright mode)',\n default: 'chromium',\n },\n headless: {\n type: 'boolean',\n description: 'Run browser in headless mode (only applies to playwright mode)',\n default: true,\n },\n url: {\n type: 'string',\n description: 'Optional URL to navigate to after launch',\n },\n profileName: {\n type: 'string',\n description: 'Optional profile name to use for browser settings and persistent storage',\n },\n vmCommand: {\n type: 'string',\n description: 'Executable/alias for vm mode launch (e.g., \"docker\" for isolated container runtime)',\n },\n vmCommandArgs: {\n type: 'array',\n description: 'Browser arguments for vm command runtime',\n items: {\n type: 'string',\n },\n },\n vmUseChromeForTesting: {\n type: 'boolean',\n description:\n 'When true, vm mode prefers Chrome for Testing. If command is omitted, \"chrome-testing\" alias, or generic \"chrome\", this resolves to Chrome for Testing.',\n default: true,\n },\n vmBrowserId: {\n type: 'string',\n description: 'Optional browserId override for vm mode',\n },\n startupDelayMs: {\n type: 'number',\n description: 'Optional startup delay in milliseconds before returning in vm mode',\n },\n vmServerUrl: {\n type: 'string',\n description:\n 'Explicit MCP server URL reachable from inside VM (e.g., \"http://host.docker.internal:3200\"). Overrides registry discovery.',\n },\n vmExtensionPath: {\n type: 'string',\n description:\n 'Explicit extension path that exists inside VM filesystem. Useful when VM does not share host workspace paths.',\n },\n vmEnableVnc: {\n type: 'boolean',\n description: 'Expose VNC/noVNC ports when using Docker VM runtime',\n default: false,\n },\n vmVncPort: {\n type: 'number',\n description: 'Host port for VNC (container port 5900) when vmEnableVnc is true',\n minimum: 1,\n maximum: 65535,\n },\n vmNoVncPort: {\n type: 'number',\n description: 'Host port for noVNC web UI (container port 7900) when vmEnableVnc is true',\n minimum: 1,\n maximum: 65535,\n },\n vmVncPassword: {\n type: 'string',\n description: 'Optional VNC password for Docker VM runtime',\n },\n baseURL: {\n type: 'string',\n description: 'Base URL for relative navigation (e.g., page.goto(\"/\") will use this as the base)',\n },\n videoDir: {\n type: 'string',\n description:\n 'Directory to save video recordings. If provided, video will be recorded for all pages. Videos are saved when the page or context is closed.',\n },\n proxy: {\n type: 'object',\n description:\n 'Proxy configuration for routing browser traffic through a proxy server. Supports authenticated residential proxies.',\n properties: {\n server: {\n type: 'string',\n description: 'Proxy server URL (e.g., \"http://proxy.example.com:8080\")',\n },\n username: {\n type: 'string',\n description: 'Proxy username for authentication',\n },\n password: {\n type: 'string',\n description: 'Proxy password for authentication',\n },\n bypass: {\n type: 'string',\n description: 'Comma-separated list of hosts to bypass the proxy (e.g., \"localhost,127.0.0.1\")',\n },\n },\n required: ['server'],\n additionalProperties: false,\n },\n },\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n /**\n * Executes the browser launch operation.\n * @param input - The input parameters for launching the browser.\n * @returns A CallToolResult with browser and page information.\n */\n async execute(input: LaunchBrowserToolInput): Promise<CallToolResult> {\n const mode = input.mode ?? 'playwright';\n\n try {\n if (mode === 'stealth') {\n return await this.launchStealthMode(input);\n }\n if (mode === 'vm') {\n return await this.launchVmMode(input);\n }\n if (mode === 'extension') {\n return await this.launchExtensionMode(input);\n }\n return await this.launchPlaywrightMode(input);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: message }],\n isError: true,\n };\n }\n }\n\n /**\n * Launch browser in Playwright mode (original behavior)\n */\n private async launchPlaywrightMode(input: LaunchBrowserToolInput): Promise<CallToolResult> {\n const launchResult = await this.browserService.launch({\n browserType: input.browserType,\n headless: input.headless,\n profileName: input.profileName,\n baseURL: input.baseURL,\n recordVideo: input.videoDir ? { dir: input.videoDir } : undefined,\n proxy: input.proxy,\n });\n\n if (input.url) {\n await launchResult.page.goto(input.url);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n browserId: launchResult.browserInstance.id,\n pageId: launchResult.pageId,\n mode: 'playwright',\n url: input.url ?? launchResult.page.url(),\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n /**\n * Launch browser in stealth mode (Chrome spawned directly without Playwright/CDP)\n */\n private async launchStealthMode(input: LaunchBrowserToolInput): Promise<CallToolResult> {\n const instance = await this.stealthLauncher.launch({\n url: input.url,\n profileName: input.profileName,\n proxy: input.proxy,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n browserId: instance.id,\n pageId: `page-${instance.id}`,\n mode: 'stealth',\n url: input.url ?? 'about:blank',\n pid: instance.pid,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n /**\n * Launch browser in extension mode (Chrome spawned directly without Playwright/CDP)\n */\n private async launchExtensionMode(input: LaunchBrowserToolInput): Promise<CallToolResult> {\n if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n console.log(\n `[ExtensionRecordingDebug] launch tool mode=extension videoDir=${input.videoDir ?? ''} url=${input.url ?? ''}`,\n );\n }\n const browserId = input.profileName\n ? `browser-${input.profileName}`\n : `${EXTENSION_BROWSER_ID_PREFIX}${randomUUID()}`;\n const navigateUrl = input.url ?? ABOUT_BLANK_URL;\n const serverUrl = await this.resolveExtensionBootstrapServerUrl();\n const bootstrapUrl = this.buildVmBootstrapUrl(serverUrl, browserId, navigateUrl);\n\n const launchResult = await this.browserService.launchWithExtension({\n url: bootstrapUrl,\n baseURL: input.baseURL,\n profileName: input.profileName,\n recordVideo: input.videoDir ? { dir: input.videoDir } : undefined,\n proxy: input.proxy,\n mode: 'extension',\n browserId,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n browserId: launchResult.browserInstance.id,\n pageId: launchResult.pageId,\n mode: 'extension',\n url: navigateUrl,\n pid: launchResult.process.pid,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n /**\n * Launch browser in vm mode (isolated runtime command)\n */\n private async launchVmMode(input: LaunchBrowserToolInput): Promise<CallToolResult> {\n const browserId = input.vmBrowserId ?? `browser-vm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n const navigateUrl = input.url ?? ABOUT_BLANK_URL;\n const vmCommand = input.vmCommand ?? 'docker';\n const serverUrl = await this.resolveVmBootstrapServerUrl(input.vmServerUrl, vmCommand);\n const bootstrapUrl = this.buildVmBootstrapUrl(serverUrl, browserId, navigateUrl);\n const dockerRuntimeOptions = this.buildVmDockerRuntimeOptions(input);\n\n const launchResult = await this.browserService.launchWithExtension({\n url: bootstrapUrl,\n baseURL: input.baseURL,\n profileName: input.profileName,\n recordVideo: input.videoDir ? { dir: input.videoDir } : undefined,\n proxy: input.proxy,\n extensionPath: input.vmExtensionPath ?? process.env.PLAYWRIGHT_VM_EXTENSION_PATH,\n mode: 'vm',\n useChromeForTesting: input.vmUseChromeForTesting ?? true,\n command: vmCommand,\n commandArgs: input.vmCommandArgs,\n dockerRunArgs: dockerRuntimeOptions.runArgs,\n dockerEnv: dockerRuntimeOptions.env,\n dockerEnableVnc: dockerRuntimeOptions.enableVnc,\n browserId,\n startupDelayMs: input.startupDelayMs,\n });\n\n const vmDetails = dockerRuntimeOptions.vnc\n ? {\n vncUrl: dockerRuntimeOptions.vnc.vncUrl,\n noVncUrl: dockerRuntimeOptions.vnc.noVncUrl,\n }\n : undefined;\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n browserId: launchResult.browserInstance.id,\n pageId: launchResult.pageId,\n mode: 'vm',\n url: navigateUrl,\n pid: launchResult.process.pid,\n ...(vmDetails ? { vm: vmDetails } : {}),\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n /**\n * Build a bootstrap URL with MCP connection metadata for VM extension startup.\n */\n private buildVmBootstrapUrl(serverUrl: string, browserId: string, navigateUrl: string): string {\n const bootstrapUrl = new URL(serverUrl);\n bootstrapUrl.searchParams.set('playwright_mcp_connect', '1');\n bootstrapUrl.searchParams.set('playwright_mcp_server', serverUrl);\n bootstrapUrl.searchParams.set('playwright_mcp_browser_id', browserId);\n bootstrapUrl.searchParams.set('playwright_mcp_navigate', navigateUrl);\n\n return bootstrapUrl.toString();\n }\n\n private async resolveVmBootstrapServerUrl(inputVmServerUrl?: string, vmCommand?: string): Promise<string> {\n const explicit = inputVmServerUrl ?? process.env.PLAYWRIGHT_VM_SERVER_URL;\n if (explicit) {\n return this.normalizeVmServerUrl(explicit);\n }\n\n const host = getPlaywrightHost();\n const registryHostPort = await this.getRegisteredPort(host);\n if (this.isDockerVmCommand(vmCommand ?? 'docker')) {\n return buildPlaywrightBaseUrl(DEFAULT_DOCKER_VM_SERVER_HOST, registryHostPort.port);\n }\n\n return buildPlaywrightBaseUrl(registryHostPort.host, registryHostPort.port);\n }\n\n /**\n * Resolve the MCP HTTP origin for extension mode bootstrap handoff.\n * Uses the registered port for the current workspace/environment and\n * falls back to the default host/port when registry lookup is unavailable.\n */\n private async resolveExtensionBootstrapServerUrl(): Promise<string> {\n const host = getPlaywrightHost();\n const registryHostPort = await this.getRegisteredPort(host);\n return buildPlaywrightBaseUrl(registryHostPort.host, registryHostPort.port);\n }\n\n private normalizeVmServerUrl(rawUrl: string): string {\n let parsed: URL;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new Error(`Invalid vmServerUrl: \"${rawUrl}\"`);\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`vmServerUrl must use http or https: \"${rawUrl}\"`);\n }\n\n return parsed.origin;\n }\n\n private isDockerVmCommand(command: string): boolean {\n return DOCKER_VM_ALIASES.has(command.trim().toLowerCase());\n }\n\n private buildVmDockerRuntimeOptions(input: LaunchBrowserToolInput): {\n runArgs?: string[];\n env?: Record<string, string>;\n enableVnc: boolean;\n vnc?: { vncUrl: string; noVncUrl: string };\n } {\n const shouldExposeVnc = input.vmEnableVnc || input.vmVncPort !== undefined || input.vmNoVncPort !== undefined;\n const runArgs: string[] = [];\n const env: Record<string, string> = {};\n\n let vnc: { vncUrl: string; noVncUrl: string } | undefined;\n if (shouldExposeVnc) {\n const hostVncPort = input.vmVncPort ?? 5901;\n const hostNoVncPort = input.vmNoVncPort ?? 7901;\n runArgs.push('-p', `${hostVncPort}:5900`, '-p', `${hostNoVncPort}:7900`);\n vnc = {\n vncUrl: `vnc://localhost:${hostVncPort}`,\n noVncUrl: `http://localhost:${hostNoVncPort}/`,\n };\n }\n\n if (input.vmVncPassword && input.vmVncPassword.trim().length > 0) {\n env.SE_VNC_PASSWORD = input.vmVncPassword.trim();\n }\n\n return {\n runArgs: runArgs.length > 0 ? runArgs : undefined,\n env: Object.keys(env).length > 0 ? env : undefined,\n enableVnc: shouldExposeVnc,\n vnc,\n };\n }\n\n /**\n * Read fallback port from env/config, with a safe hardcoded default.\n */\n private getFallbackPort(): number {\n try {\n return getPlaywrightPort();\n } catch {\n return DEFAULT_MCP_PORT;\n }\n }\n\n /**\n * Type guard for registry port values.\n */\n private isValidPort(port: unknown): port is number {\n return typeof port === 'number' && Number.isInteger(port) && port > 0 && port <= 65535;\n }\n\n /**\n * Select the most recently updated allocation entry.\n */\n private getLatestRegistryEntry(entries: PortRegistryRecord[]): PortRegistryRecord | null {\n if (entries.length === 0) {\n return null;\n }\n\n return (\n [...entries].sort((a, b) => {\n const aUpdatedAt = Number.isNaN(Date.parse(a.updatedAt)) ? 0 : Date.parse(a.updatedAt);\n const bUpdatedAt = Number.isNaN(Date.parse(b.updatedAt)) ? 0 : Date.parse(b.updatedAt);\n return bUpdatedAt - aUpdatedAt;\n })[0] ?? null\n );\n }\n\n /**\n * Resolve the MCP HTTP endpoint from the port registry.\n * First attempts a repo-scoped lookup, then falls back to global lookup\n * so extension bootstrap still works across git worktrees.\n */\n private async getRegisteredPort(host: string): Promise<{ host: string; port: number }> {\n const fallback = { host, port: this.getFallbackPort() };\n const repositoryPath = resolveWorkspaceRoot(process.cwd());\n const environment = process.env.NODE_ENV || DEFAULT_ENVIRONMENT;\n const portRegistry = new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n\n try {\n const scopedResult = await portRegistry.getPort({\n repositoryPath,\n serviceName: PLAYWRIGHT_HTTP_SERVICE_NAME,\n serviceType: PLAYWRIGHT_HTTP_SERVICE_TYPE,\n environment,\n });\n\n if (scopedResult.success && scopedResult.record && this.isValidPort(scopedResult.record.port)) {\n return {\n host: scopedResult.record.host || host,\n port: scopedResult.record.port,\n };\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.warn(\n `${LOG_PREFIX} Scoped registry lookup failed for ${PLAYWRIGHT_HTTP_SERVICE_NAME} in ${environment}: ${errorMessage}`,\n );\n }\n\n try {\n // Global lookup supports cross-worktree launches where repositoryPath differs.\n const allocations = await portRegistry.listAllocations({\n serviceName: PLAYWRIGHT_HTTP_SERVICE_NAME,\n serviceType: PLAYWRIGHT_HTTP_SERVICE_TYPE,\n environment,\n });\n const latestRecord = this.getLatestRegistryEntry(allocations);\n\n if (!latestRecord || !this.isValidPort(latestRecord.port)) {\n return fallback;\n }\n\n return {\n host: latestRecord.host || host,\n port: latestRecord.port,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.warn(\n `${LOG_PREFIX} Global registry lookup failed for ${PLAYWRIGHT_HTTP_SERVICE_NAME} in ${environment}: ${errorMessage}`,\n );\n return fallback;\n }\n }\n}\n","/**\n * ListConsoleMessagesTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to PageMonitorService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageMonitorService } from '../services/PageMonitorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ListConsoleMessagesToolInput extends PageTargetInput {\n /** Filter by message type (log, info, warning, error, debug, etc.) */\n type?: string;\n /** Maximum number of messages to return */\n limit?: number;\n}\n\n@injectable()\nexport class ListConsoleMessagesTool extends BaseTool<ListConsoleMessagesToolInput> {\n static readonly TOOL_NAME = 'browser_list_console_messages';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.PageMonitorService) private readonly pageMonitor: IPageMonitorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ListConsoleMessagesTool.TOOL_NAME,\n description:\n 'Lists all console messages captured during page automation. Requires monitoring to be started first.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n type: {\n type: 'string',\n description: 'Filter by message type (log, info, warning, error, debug, trace, etc.)',\n enum: ['log', 'info', 'warning', 'error', 'debug', 'trace', 'dir', 'dirxml', 'table', 'count', 'assert'],\n },\n limit: {\n type: 'number',\n description: 'Maximum number of messages to return',\n minimum: 1,\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ListConsoleMessagesToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n return this.executeWithMode(\n ListConsoleMessagesTool.TOOL_NAME,\n pageEntry.id,\n input as unknown as Record<string, unknown>,\n async () => {\n let messages = this.pageMonitor.getConsoleMessages(pageEntry.id, input.type);\n\n if (input.limit) {\n messages = messages.slice(0, input.limit);\n }\n\n return this.successJson({\n pageId: pageEntry.id,\n messageCount: messages.length,\n messages: messages.map((m) => ({\n id: m.id,\n type: m.type,\n text: m.text,\n location: m.location,\n timestamp: m.timestamp.toISOString(),\n })),\n });\n },\n );\n });\n }\n}\n","/**\n * ListNetworkRequestsTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to PageMonitorService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageMonitorService } from '../services/PageMonitorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ListNetworkRequestsToolInput extends PageTargetInput {\n /** Filter by resource type (e.g., 'document', 'xhr', 'fetch', 'script', 'stylesheet') */\n resourceType?: string;\n /** Filter by URL pattern (substring match) */\n urlPattern?: string;\n /** Filter by HTTP method */\n method?: string;\n /** Maximum number of requests to return */\n limit?: number;\n}\n\n@injectable()\nexport class ListNetworkRequestsTool extends BaseTool<ListNetworkRequestsToolInput> {\n static readonly TOOL_NAME = 'browser_list_network_requests';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.PageMonitorService) private readonly pageMonitor: IPageMonitorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ListNetworkRequestsTool.TOOL_NAME,\n description:\n 'Lists all network requests captured during page automation. Requires monitoring to be started first.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n resourceType: {\n type: 'string',\n description: 'Filter by resource type (document, xhr, fetch, script, stylesheet, image, etc.)',\n },\n urlPattern: {\n type: 'string',\n description: 'Filter by URL pattern (substring match)',\n },\n method: {\n type: 'string',\n description: 'Filter by HTTP method (GET, POST, etc.)',\n },\n limit: {\n type: 'number',\n description: 'Maximum number of requests to return',\n minimum: 1,\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ListNetworkRequestsToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n return this.executeWithMode(\n ListNetworkRequestsTool.TOOL_NAME,\n pageEntry.id,\n input as unknown as Record<string, unknown>,\n async () => {\n let requests = this.pageMonitor.getNetworkRequests(pageEntry.id);\n\n if (input.resourceType) {\n requests = requests.filter((r) => r.resourceType === input.resourceType);\n }\n if (input.urlPattern) {\n const pattern = input.urlPattern;\n requests = requests.filter((r) => r.url.includes(pattern));\n }\n if (input.method) {\n requests = requests.filter((r) => r.method === input.method);\n }\n if (input.limit) {\n requests = requests.slice(0, input.limit);\n }\n\n return this.successJson({\n pageId: pageEntry.id,\n requestCount: requests.length,\n requests: requests.map((r) => ({\n id: r.id,\n url: r.url,\n method: r.method,\n resourceType: r.resourceType,\n status: r.response?.status,\n statusText: r.response?.statusText,\n timing: r.response?.timing,\n timestamp: r.timestamp.toISOString(),\n })),\n });\n },\n );\n });\n }\n}\n","/**\n * ListPagesTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to PageRegistry and SessionService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface ListPagesToolInput {\n /** Browser ID to list pages for */\n browserId: string;\n}\n\n@injectable()\nexport class ListPagesTool extends BaseTool<ListPagesToolInput> {\n static readonly TOOL_NAME = 'browser_list_pages';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ListPagesTool.TOOL_NAME,\n description:\n 'Lists all pages (tabs) in a browser. Returns page IDs, URLs, titles, and indicates which page is currently active.',\n inputSchema: {\n type: 'object',\n properties: {\n browserId: {\n type: 'string',\n description: 'Browser ID to list pages for',\n },\n },\n required: ['browserId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ListPagesToolInput): Promise<CallToolResult> {\n return this.toolExecutor.executeForBrowser(\n ListPagesTool.TOOL_NAME,\n input.browserId,\n input as unknown as Record<string, unknown>,\n async () => {\n const browser = this.browserService.getBrowser(input.browserId);\n if (!browser) {\n return this.error(`Browser \"${input.browserId}\" not found`);\n }\n\n const pages = this.pageRegistry.findByBrowser(input.browserId);\n\n const pageList = await Promise.all(\n pages.map(async (entry) => {\n await this.pageRegistry.updateMetadata(entry.id);\n const updated = this.pageRegistry.get(entry.id);\n\n return {\n pageId: entry.id,\n url: updated?.url ?? entry.url,\n title: updated?.title ?? entry.title,\n isActive: entry.id === browser.currentPageId,\n browserId: entry.browserId,\n profileName: entry.profileName,\n createdAt: entry.createdAt.toISOString(),\n };\n }),\n );\n\n return this.successJson({\n browserId: input.browserId,\n currentPageId: browser.currentPageId,\n pageCount: pageList.length,\n pages: pageList,\n });\n },\n );\n }\n}\n","/**\n * ListProfilesTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with try/catch\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IProfileService } from '../services/ProfileService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport type ListProfilesToolInput = {};\n\n@injectable()\nexport class ListProfilesTool implements Tool<ListProfilesToolInput> {\n static readonly TOOL_NAME = 'browser_list_profiles';\n\n constructor(@inject(PLAYWRIGHT_TYPES.ProfileService) private readonly profileService: IProfileService) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: ListProfilesTool.TOOL_NAME,\n description: 'Lists all available browser profiles with their settings, browser type, viewport, and timestamps.',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n try {\n const profiles = await this.profileService.list();\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n profileCount: profiles.length,\n profiles: profiles.map((p) => ({\n name: p.name,\n browserType: p.browserType,\n viewport: p.viewport ?? null,\n userAgent: p.userAgent ?? null,\n locale: p.locale ?? null,\n timezone: p.timezone ?? null,\n colorScheme: p.colorScheme ?? null,\n createdAt: p.createdAt,\n updatedAt: p.updatedAt,\n })),\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","import 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { ICodeSnippetService } from '../services/CodeSnippetService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport type ListSnippetsToolInput = {};\n\n@injectable()\nexport class ListSnippetsTool implements Tool<ListSnippetsToolInput> {\n static readonly TOOL_NAME = 'browser_list_snippets';\n\n constructor(@inject(PLAYWRIGHT_TYPES.CodeSnippetService) private readonly snippetService: ICodeSnippetService) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: ListSnippetsTool.TOOL_NAME,\n description: 'Lists saved code snippets by name, description, and snippetPath for reuse with browser_run_code.',\n inputSchema: {\n type: 'object',\n properties: {},\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(): Promise<CallToolResult> {\n try {\n const snippets = await this.snippetService.listSnippets();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n snippetCount: snippets.length,\n snippets,\n },\n null,\n 2,\n ),\n },\n ],\n };\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n}\n","/**\n * NavigateTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface NavigateToolInput extends PageTargetInput {\n /** URL to navigate to */\n url: string;\n /** Wait until condition: 'load', 'domcontentloaded', 'networkidle', or 'commit' */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';\n /** Timeout in milliseconds */\n timeout?: number;\n /** Referer header value */\n referer?: string;\n}\n\n@injectable()\nexport class NavigateTool extends BaseTool<NavigateToolInput> {\n static readonly TOOL_NAME = 'browser_navigate';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: NavigateTool.TOOL_NAME,\n description: 'Navigates the browser to a specified URL.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n url: { type: 'string', description: 'URL to navigate to' },\n waitUntil: {\n type: 'string',\n enum: ['load', 'domcontentloaded', 'networkidle', 'commit'],\n description: 'Wait until condition',\n default: 'load',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n referer: { type: 'string', description: 'Referer header value' },\n },\n required: ['pageId', 'url'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: NavigateToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n NavigateTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const response = await page.goto(input.url, {\n waitUntil: input.waitUntil,\n timeout: input.timeout,\n referer: input.referer,\n });\n\n const status = response?.status() ?? 'unknown';\n return this.success(`Navigated to ${input.url} (status: ${status})`);\n },\n );\n }\n}\n","/**\n * NewPageTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to BrowserService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface NewPageToolInput {\n /** Browser ID to create the page in (optional - uses default browser if not specified) */\n browserId?: string;\n /** Optional URL to navigate to after creation */\n url?: string;\n /** Whether to set as current page for the browser (default: true) */\n setAsCurrent?: boolean;\n}\n\n@injectable()\nexport class NewPageTool extends BaseTool<NewPageToolInput> {\n static readonly TOOL_NAME = 'browser_new_page';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: NewPageTool.TOOL_NAME,\n description:\n 'Creates a new page (tab) in a browser instance. Uses the default browser if browserId is not specified, launching one if needed.',\n inputSchema: {\n type: 'object',\n properties: {\n browserId: {\n type: 'string',\n description: 'Browser ID to create the page in (optional - uses default browser if not specified)',\n },\n url: {\n type: 'string',\n description: 'Optional URL to navigate to after creation',\n },\n setAsCurrent: {\n type: 'boolean',\n description: 'Whether to set as current page for the browser (default: true)',\n default: true,\n },\n },\n required: [],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: NewPageToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const browserInstance = input.browserId\n ? this.browserService.getBrowser(input.browserId)\n : await this.browserService.getOrCreateDefaultBrowser();\n\n if (!browserInstance) {\n return this.error(`Browser \"${input.browserId}\" not found`);\n }\n\n const browserId = browserInstance.id;\n const setAsCurrent = input.setAsCurrent !== false;\n\n if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n const pageId = this.pageRegistry.registerExtensionPage(browserId, undefined, input.url, false);\n const result = await this.toolExecutor.executeForBrowser(\n NewPageTool.TOOL_NAME,\n browserId,\n { ...input, pageId },\n async () => this.error(`Browser \"${browserId}\" is not in extension mode`),\n );\n\n if (result.isError) {\n this.pageRegistry.remove(pageId);\n return result;\n }\n\n browserInstance.pageIds.add(pageId);\n if (setAsCurrent || !browserInstance.currentPageId) {\n browserInstance.currentPageId = pageId;\n }\n\n const delegatedText = result.content[0]?.type === 'text' ? result.content[0].text : undefined;\n let delegatedPayload: { url?: string; title?: string; tabId?: number } = {};\n if (typeof delegatedText === 'string') {\n try {\n delegatedPayload = JSON.parse(delegatedText) as { url?: string; title?: string; tabId?: number };\n } catch {\n delegatedPayload = {};\n }\n }\n const pageEntry = this.pageRegistry.get(pageId);\n if (pageEntry) {\n pageEntry.url = delegatedPayload.url ?? pageEntry.url;\n pageEntry.title = delegatedPayload.title ?? pageEntry.title;\n pageEntry.extensionTabId = delegatedPayload.tabId ?? pageEntry.extensionTabId;\n }\n\n this.browserService.recordBrowserActivity(browserId, pageId);\n\n return this.successJson({\n pageId,\n url: pageEntry?.url ?? input.url ?? 'about:blank',\n title: pageEntry?.title ?? '',\n browserId,\n isActive: browserInstance.currentPageId === pageId,\n });\n }\n\n const { pageId, page } = await this.browserService.newPage(browserId);\n\n if (input.url) {\n try {\n await page.goto(input.url);\n await this.pageRegistry.updateMetadata(pageId);\n } catch (navError) {\n await page.close();\n throw navError;\n }\n }\n\n if (setAsCurrent) {\n this.browserService.setCurrentPage(browserId, pageId);\n }\n\n const pageEntry = this.pageRegistry.get(pageId);\n\n return this.successJson({\n pageId,\n url: pageEntry?.url ?? page.url(),\n title: pageEntry?.title ?? '',\n browserId,\n isActive: setAsCurrent,\n });\n });\n }\n}\n","/**\n * PdfTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n * - Saves PDF to temp directory with returned path\n */\n\nimport 'reflect-metadata/lite';\nimport { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface PdfToolInput extends PageTargetInput {\n /** Paper format */\n format?: 'Letter' | 'Legal' | 'Tabloid' | 'Ledger' | 'A0' | 'A1' | 'A2' | 'A3' | 'A4' | 'A5' | 'A6';\n /** Print background graphics */\n printBackground?: boolean;\n /** Landscape orientation */\n landscape?: boolean;\n /** Page scale (0.1 to 2) */\n scale?: number;\n /** Top margin (e.g., '1cm', '0.5in') */\n marginTop?: string;\n /** Bottom margin */\n marginBottom?: string;\n /** Left margin */\n marginLeft?: string;\n /** Right margin */\n marginRight?: string;\n /** Custom filename (without extension) */\n filename?: string;\n}\n\n@injectable()\nexport class PdfTool extends BaseTool<PdfToolInput> {\n static readonly TOOL_NAME = 'browser_pdf';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ExtensionTaskQueue) private readonly extensionTaskQueue: ExtensionTaskQueue,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PdfTool.TOOL_NAME,\n description:\n 'Generates a PDF of the page with configurable format options. Saves to temp directory and returns the file path.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n format: {\n type: 'string',\n enum: ['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6'],\n description: 'Paper format',\n default: 'Letter',\n },\n printBackground: { type: 'boolean', description: 'Print background graphics', default: false },\n landscape: { type: 'boolean', description: 'Landscape orientation', default: false },\n scale: { type: 'number', description: 'Page scale (0.1 to 2)', minimum: 0.1, maximum: 2, default: 1 },\n marginTop: { type: 'string', description: 'Top margin (e.g., \"1cm\", \"0.5in\")' },\n marginBottom: { type: 'string', description: 'Bottom margin' },\n marginLeft: { type: 'string', description: 'Left margin' },\n marginRight: { type: 'string', description: 'Right margin' },\n filename: { type: 'string', description: 'Custom filename without extension' },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PdfToolInput): Promise<CallToolResult> {\n const pageEntry = this.getPage(input.pageId);\n if (!pageEntry) {\n return this.error(`Page \"${input.pageId}\" not found`);\n }\n\n if (pageEntry.mode === 'extension') {\n return this.safeExecute(async () => {\n const filename = input.filename ?? `page-${randomUUID()}`;\n const filePath = join(tmpdir(), `${filename}.pdf`);\n const taskResult = await this.extensionTaskQueue.queueTask(\n PdfTool.TOOL_NAME,\n {\n pageId: input.pageId,\n format: input.format,\n printBackground: input.printBackground,\n landscape: input.landscape,\n scale: input.scale,\n marginTop: input.marginTop,\n marginBottom: input.marginBottom,\n marginLeft: input.marginLeft,\n marginRight: input.marginRight,\n filename,\n },\n undefined,\n pageEntry.browserId,\n );\n const payload = JSON.parse((taskResult.result?.content?.[0] as { text?: string })?.text ?? '{}') as {\n pdfBase64?: string;\n };\n if (!payload.pdfBase64) {\n throw new Error('Extension mode PDF generation returned no data');\n }\n\n await writeFile(filePath, Buffer.from(payload.pdfBase64, 'base64'));\n return this.success(`PDF saved to: ${filePath}`);\n });\n }\n\n return this.executeWithMode(\n PdfTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n const filename = input.filename ?? `page-${randomUUID()}`;\n const filePath = join(tmpdir(), `${filename}.pdf`);\n\n await page.pdf({\n path: filePath,\n format: input.format ?? 'Letter',\n printBackground: input.printBackground ?? false,\n landscape: input.landscape ?? false,\n scale: input.scale ?? 1,\n margin: {\n top: input.marginTop,\n bottom: input.marginBottom,\n left: input.marginLeft,\n right: input.marginRight,\n },\n });\n\n return this.success(`PDF saved to: ${filePath}`);\n },\n );\n }\n}\n","/**\n * PressKeyTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface PressKeyToolInput extends PageTargetInput {\n /** Key or key combination to press (e.g., 'Enter', 'Control+a', 'Meta+Shift+t') */\n key: string;\n /** Optional element to focus before pressing key */\n element?: ElementSelector;\n /** Delay in milliseconds between keydown and keyup */\n delay?: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class PressKeyTool extends BaseTool<PressKeyToolInput> {\n static readonly TOOL_NAME = 'browser_press_key';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: PressKeyTool.TOOL_NAME,\n description: 'Presses a keyboard key or key combination. Supports modifier keys like Control, Shift, Alt, Meta.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n key: {\n type: 'string',\n description: 'Key or key combination (e.g., \"Enter\", \"Tab\", \"Control+a\", \"Meta+Shift+t\", \"ArrowDown\")',\n },\n element: {\n type: 'object',\n description: 'Optional element to focus before pressing key',\n properties: {\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n },\n },\n delay: { type: 'number', description: 'Delay between keydown and keyup in ms' },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId', 'key'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: PressKeyToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n PressKeyTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n if (input.element) {\n const locator = await this.elementLocator.locate(page, input.element);\n await locator.press(input.key, { delay: input.delay, timeout: input.timeout });\n } else {\n await page.keyboard.press(input.key, { delay: input.delay });\n }\n\n return this.success(`Pressed key: \"${input.key}\"`);\n },\n );\n }\n}\n","/**\n * ReloadTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ReloadToolInput extends PageTargetInput {\n /** Wait until condition: 'load', 'domcontentloaded', 'networkidle', or 'commit' */\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class ReloadTool extends BaseTool<ReloadToolInput> {\n static readonly TOOL_NAME = 'browser_reload';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ReloadTool.TOOL_NAME,\n description: 'Reloads the current page.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n waitUntil: {\n type: 'string',\n enum: ['load', 'domcontentloaded', 'networkidle', 'commit'],\n description: 'Wait until condition',\n default: 'load',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ReloadToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n ReloadTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const response = await page.reload({\n waitUntil: input.waitUntil,\n timeout: input.timeout,\n });\n\n const status = response?.status() ?? 'unknown';\n return this.success(`Page reloaded (status: ${status})`);\n },\n );\n }\n}\n","/**\n * ResizePageTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ResizePageToolInput extends PageTargetInput {\n /** Viewport width in pixels */\n width: number;\n /** Viewport height in pixels */\n height: number;\n /** Device scale factor (default: 1) */\n deviceScaleFactor?: number;\n /** Whether the meta viewport tag is taken into account (default: false) */\n isMobile?: boolean;\n /** Whether the page is in landscape mode (default: false) */\n hasTouch?: boolean;\n}\n\n@injectable()\nexport class ResizePageTool extends BaseTool<ResizePageToolInput> {\n static readonly TOOL_NAME = 'browser_resize_page';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ResizePageTool.TOOL_NAME,\n description: 'Resizes the viewport of a page to specified width and height dimensions.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n width: {\n type: 'number',\n description: 'Viewport width in pixels',\n minimum: 1,\n },\n height: {\n type: 'number',\n description: 'Viewport height in pixels',\n minimum: 1,\n },\n deviceScaleFactor: {\n type: 'number',\n description: 'Device scale factor (default: 1)',\n minimum: 1,\n maximum: 3,\n },\n isMobile: {\n type: 'boolean',\n description: 'Whether the meta viewport tag is taken into account',\n },\n hasTouch: {\n type: 'boolean',\n description: 'Whether the viewport supports touch events',\n },\n },\n required: ['pageId', 'width', 'height'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ResizePageToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n ResizePageTool.TOOL_NAME,\n input.pageId,\n { width: input.width, height: input.height },\n async () => {\n const page = this.resolvePage(input.pageId);\n\n await page.setViewportSize({\n width: input.width,\n height: input.height,\n });\n\n // Get the actual viewport size after setting\n const viewportSize = page.viewportSize();\n\n return this.successJson({\n success: true,\n viewport: {\n width: viewportSize?.width ?? input.width,\n height: viewportSize?.height ?? input.height,\n },\n });\n },\n );\n }\n}\n","/**\n * RunCodeTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ICodeSnippetService, SavedCodeSnippet } from '../services/CodeSnippetService.js';\nimport type { IExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface RunCodeToolInput extends PageTargetInput {\n /** JavaScript code string containing an async function that receives { page, context, browser } */\n code?: string;\n /** Saved snippet path to execute instead of inline code */\n snippetPath?: string;\n /** Optional save target for inline code */\n saveAs?: {\n name: string;\n description: string;\n };\n}\n\n/**\n * Tool for running arbitrary Playwright code snippets.\n * Accepts a JavaScript function that receives the page object and can perform any Playwright operation.\n */\n@injectable()\nexport class RunCodeTool extends BaseTool<RunCodeToolInput> {\n static readonly TOOL_NAME = 'browser_run_code';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ExtensionPageProxy) private readonly extensionPageProxy: IExtensionPageProxy,\n @inject(PLAYWRIGHT_TYPES.CodeSnippetService) private readonly snippetService: ICodeSnippetService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n /**\n * Returns the tool definition including name, description, and input schema.\n * @returns The tool definition for MCP registration.\n */\n getDefinition(): ToolDefinition {\n return {\n name: RunCodeTool.TOOL_NAME,\n description:\n 'Runs a Playwright code snippet. The code should be an async function body that receives { page, context, browser } and can perform any Playwright operation. Return a value to include it in the response.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n code: {\n type: 'string',\n description:\n 'JavaScript code to execute. Receives { page, context, browser } as arguments. Example: \"await page.goto(\\'https://example.com\\'); return await page.title();\"',\n },\n snippetPath: {\n type: 'string',\n description:\n 'Relative snippet path inside the configured snippets directory. When provided, executes the saved snippet instead of inline code.',\n },\n saveAs: {\n type: 'object',\n description:\n 'Optional snippet metadata used to save the inline code into the configured snippets directory.',\n properties: {\n name: {\n type: 'string',\n description:\n 'Human-readable snippet name. The filename is generated from a sanitized version of this value.',\n },\n description: {\n type: 'string',\n description: 'Short description shown by browser_list_snippets.',\n },\n },\n required: ['name', 'description'],\n additionalProperties: false,\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n /**\n * Executes the provided Playwright code snippet.\n * @param input - The input parameters including sessionId and code.\n * @returns A CallToolResult with the execution result or error.\n */\n async execute(input: RunCodeToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n if (!input.code && !input.snippetPath) {\n return this.error('Either \"code\" or \"snippetPath\" is required');\n }\n if (input.saveAs && !input.code) {\n return this.error('\"saveAs\" requires inline \"code\"');\n }\n\n const pageEntry = this.resolvePageEntry(input.pageId);\n const { page, context, browser } = pageEntry;\n const runtimePage =\n pageEntry.mode === 'extension'\n ? (() => {\n this.extensionPageProxy.setTarget(pageEntry.id, pageEntry.browserId);\n return this.extensionPageProxy;\n })()\n : page;\n const runtimeContext = pageEntry.mode === 'extension' ? this.extensionPageProxy.context() : context;\n const runtimeBrowser =\n pageEntry.mode === 'extension'\n ? {\n id: pageEntry.browserId,\n mode: this.browserService.getBrowser(pageEntry.browserId)?.mode ?? 'extension',\n pageId: pageEntry.id,\n }\n : browser;\n\n let savedSnippet: SavedCodeSnippet | undefined;\n if (input.saveAs) {\n savedSnippet = await this.snippetService.saveSnippet({\n name: input.saveAs.name,\n description: input.saveAs.description,\n code: input.code as string,\n });\n }\n\n const result = input.snippetPath\n ? await this.snippetService.loadSnippet(input.snippetPath).then((snippet) =>\n snippet.run({\n page: runtimePage,\n context: runtimeContext,\n browser: runtimeBrowser,\n }),\n )\n : await (\n new Function('page', 'context', 'browser', `return (async () => { ${input.code} })();`) as (\n page: typeof runtimePage,\n context: typeof runtimeContext,\n browser: typeof runtimeBrowser,\n ) => Promise<unknown>\n )(runtimePage, runtimeContext, runtimeBrowser);\n\n if (result === undefined) {\n if (savedSnippet) {\n return this.successJson({ savedSnippet });\n }\n return this.success('Code executed successfully');\n }\n\n return this.successJson(savedSnippet ? { result, savedSnippet } : { result });\n });\n }\n}\n","/**\n * RunSpecTool\n *\n * DESIGN PATTERNS:\n * - Tool pattern with getDefinition() and execute() methods\n * - Service delegation for business logic\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Implement Tool interface from ../types\n * - Use TOOL_NAME constant with snake_case (e.g., 'run_spec')\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n * - Delegate complex logic to AutomationRunner service\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IAutomationRunner } from '../services/AutomationRunner.js';\nimport type { IExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { ExtensionSpecExecutionResult, IExtensionSpecRunner } from '../services/ExtensionSpecRunner.js';\nimport type { ISpecBundlerService } from '../services/SpecBundlerService.js';\nimport type { ISpecMetadataService } from '../services/SpecMetadataService.js';\nimport type { TestFilter, Tool, ToolDefinition } from '../types/index.js';\n\n/** Execution mode for running specs */\nexport type SpecExecutionMode = 'playwright' | 'extension';\n\n/** Result returned by a hooks file's setup() function */\nexport interface HooksResult {\n baseURL?: string;\n}\n\nconst HOOKS_FILENAME = 'run-spec-hooks.ts';\nconst MAX_HOOKS_SEARCH_DEPTH = 4;\nconst DEFAULT_VIDEO_SIZE = { width: 1920, height: 1080 };\n\ninterface RunSpecToolInput {\n /** Path to the Playwright spec file */\n specPath: string;\n /** Optional automation session ID */\n automationId?: string;\n /** Execution mode: 'playwright' for standard Playwright, 'extension' for Chrome extension mode */\n mode?: SpecExecutionMode;\n /** Browser ID to use (required for extension mode) */\n browserId?: string;\n /** Page ID to use (required for extension mode) */\n pageId?: string;\n /** Browser type to use */\n browserType?: 'chromium' | 'firefox' | 'webkit';\n /** Run browser in headless mode */\n headless?: boolean;\n /** Base URL for relative navigation (e.g., page.goto('/')) */\n baseURL?: string;\n /** Keep browser open after spec execution completes */\n keepBrowserOpen?: boolean;\n /** Path to playwright.config.ts for loading project settings */\n configPath?: string;\n /** Explicit path to run-spec-hooks.ts file. Skips auto-discovery when provided. */\n hooksPath?: string;\n /** Environment variables to set before running the spec. Applied before hooks execution. */\n env?: Record<string, string>;\n /** Run only the test with this exact name */\n testName?: string;\n /** Run only tests matching this regex pattern */\n testPattern?: string;\n /** Run only tests marked with test.only() */\n onlyMarked?: boolean;\n /** Run only tests in describe blocks matching this pattern */\n describeFilter?: string;\n /** Arguments to pass to the spec (available as fixtures.specArgs) */\n specArgs?: Record<string, unknown>;\n /** Load specArgs from environment variables using spec's envPrefix */\n loadArgsFromEnv?: boolean;\n /** Directory to save video recordings. Supported in both playwright and extension modes. */\n videoDir?: string;\n}\n\n@injectable()\nexport class RunSpecTool implements Tool<RunSpecToolInput> {\n static readonly TOOL_NAME = 'run_spec';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.AutomationRunner) private automationRunner: IAutomationRunner,\n @inject(PLAYWRIGHT_TYPES.SpecMetadataService) private specMetadataService: ISpecMetadataService,\n @inject(PLAYWRIGHT_TYPES.ExtensionSpecRunner) private extensionSpecRunner: IExtensionSpecRunner,\n @inject(PLAYWRIGHT_TYPES.ExtensionPageProxy) private extensionPageProxy: IExtensionPageProxy,\n @inject(PLAYWRIGHT_TYPES.SpecBundlerService) private specBundlerService: ISpecBundlerService,\n ) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: RunSpecTool.TOOL_NAME,\n description:\n 'Runs a Playwright spec file with optional test filtering and dynamic arguments. Supports filtering by test name, pattern, or test.only markers.',\n inputSchema: {\n type: 'object',\n properties: {\n specPath: {\n type: 'string',\n description: 'Absolute path to the Playwright spec file to execute',\n },\n automationId: {\n type: 'string',\n description: 'Optional automation session ID (auto-generated if not provided)',\n },\n mode: {\n type: 'string',\n enum: ['playwright', 'extension'],\n description: 'Execution mode: playwright (standard) or extension (Chrome extension)',\n default: 'playwright',\n },\n browserId: {\n type: 'string',\n description: 'Browser ID to use (required for extension mode)',\n },\n pageId: {\n type: 'string',\n description: 'Page ID to use (required for extension mode)',\n },\n browserType: {\n type: 'string',\n enum: ['chromium', 'firefox', 'webkit'],\n description: 'Browser type to use (only applies to playwright mode)',\n default: 'chromium',\n },\n headless: {\n type: 'boolean',\n description: 'Run browser in headless mode (only applies to playwright mode)',\n default: true,\n },\n baseURL: {\n type: 'string',\n description: 'Base URL for relative navigation (e.g., page.goto(\"/\") will use this as the base)',\n },\n keepBrowserOpen: {\n type: 'boolean',\n description: 'Keep browser open after spec execution completes for further interaction',\n default: false,\n },\n hooksPath: {\n type: 'string',\n description:\n 'Explicit absolute path to run-spec-hooks.ts file. Skips auto-discovery when provided. The source directory is injected as process.env.__HOOKS_SOURCE_DIR__ so hooks can resolve paths relative to their original location.',\n },\n env: {\n type: 'object',\n description:\n 'Environment variables to set before running the spec. Applied before hooks execution. Useful for passing config like API endpoints without relying on hooks file path resolution.',\n additionalProperties: { type: 'string' },\n },\n testName: {\n type: 'string',\n description: 'Filter by exact test name',\n },\n testPattern: {\n type: 'string',\n description: 'Filter by regex pattern on full test title (e.g., \"login.*success\")',\n },\n onlyMarked: {\n type: 'boolean',\n description: 'Run only tests marked with test.only()',\n default: false,\n },\n describeFilter: {\n type: 'string',\n description: 'Filter by describe block name pattern',\n },\n specArgs: {\n type: 'object',\n description: 'Arguments to pass to the spec (available as fixtures.specArgs)',\n additionalProperties: true,\n },\n loadArgsFromEnv: {\n type: 'boolean',\n description: \"Load specArgs from environment variables using spec's envPrefix export\",\n default: false,\n },\n videoDir: {\n type: 'string',\n description:\n 'Absolute directory path to save video recordings. Supported in both playwright and extension modes.',\n },\n },\n required: ['specPath'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: RunSpecToolInput): Promise<CallToolResult> {\n if (!isAbsolute(input.specPath)) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: specPath must be an absolute path (e.g., /Users/me/project/tests/my-spec.ts)',\n },\n ],\n isError: true,\n };\n }\n\n if (input.hooksPath && !isAbsolute(input.hooksPath)) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: hooksPath must be an absolute path',\n },\n ],\n isError: true,\n };\n }\n\n if (input.videoDir && !isAbsolute(input.videoDir)) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: videoDir must be an absolute path',\n },\n ],\n isError: true,\n };\n }\n\n try {\n if (input.env) {\n for (const [key, value] of Object.entries(input.env)) {\n process.env[key] = value;\n }\n }\n\n // Discover hooks file path — it will be bundled into the spec\n // so setup() runs before any spec module-level code.\n // We still run hooks separately to extract baseURL for the browser context.\n const resolvedHooksPath = this.resolveHooksPath(input.specPath, input.hooksPath);\n const hooksResult = resolvedHooksPath ? await this.runHooks(resolvedHooksPath) : null;\n if (hooksResult?.baseURL && !input.baseURL) {\n input.baseURL = hooksResult.baseURL;\n }\n\n const mode = input.mode ?? 'playwright';\n\n if (mode === 'extension') {\n return await this.executeExtensionMode(input);\n }\n\n const hasEnhancedOptions =\n input.testName || input.testPattern || input.onlyMarked || input.describeFilter || input.loadArgsFromEnv;\n\n if (hasEnhancedOptions) {\n return await this.executeEnhanced(input, resolvedHooksPath);\n }\n\n return await this.executeBasic(input, resolvedHooksPath);\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n /**\n * Finds a run-spec-hooks.ts file by walking up from the spec directory\n * up to MAX_HOOKS_SEARCH_DEPTH levels. Returns the first match found.\n */\n private findHooksFile(specPath: string): string | null {\n let currentDir = dirname(specPath);\n\n for (let depth = 0; depth < MAX_HOOKS_SEARCH_DEPTH; depth++) {\n const hooksPath = join(currentDir, HOOKS_FILENAME);\n if (existsSync(hooksPath)) {\n return hooksPath;\n }\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) break;\n currentDir = parentDir;\n }\n\n return null;\n }\n\n /**\n * Bundles and executes the hooks file, calling its setup() export.\n * Injects the original source directory as process.env.__HOOKS_SOURCE_DIR__\n * so hooks can resolve paths relative to their original location.\n */\n private async runHooks(hooksPath: string): Promise<HooksResult | undefined> {\n const sourceDir = dirname(resolve(hooksPath));\n const bundleResult = await this.specBundlerService.bundle(hooksPath);\n try {\n process.env.__HOOKS_SOURCE_DIR__ = sourceDir;\n const hooksModule = await import(`${bundleResult.outputPath}?t=${Date.now()}`);\n if (typeof hooksModule.setup === 'function') {\n return await hooksModule.setup();\n }\n return undefined;\n } finally {\n process.env.__HOOKS_SOURCE_DIR__ = undefined;\n await bundleResult.cleanup();\n }\n }\n\n /**\n * Resolves the hooks file path for a spec. Returns absolute path or null.\n * When explicitHooksPath is provided, skips auto-discovery.\n */\n private resolveHooksPath(specPath: string, explicitHooksPath?: string): string | null {\n return explicitHooksPath ? resolve(explicitHooksPath) : this.findHooksFile(specPath);\n }\n\n private async executeExtensionMode(input: RunSpecToolInput): Promise<CallToolResult> {\n if (!input.pageId || !input.browserId) {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: pageId and browserId are required for extension mode',\n },\n ],\n isError: true,\n };\n }\n\n this.extensionPageProxy.setTarget(input.pageId, input.browserId);\n\n let specArgs = input.specArgs ?? {};\n\n if (input.loadArgsFromEnv) {\n const metadata = await this.specMetadataService.extractSpecMetadata(input.specPath);\n if (metadata.argsSchema && metadata.envPrefix) {\n const envArgs = this.specMetadataService.parseArgsFromEnv(metadata.argsSchema, metadata.envPrefix);\n specArgs = { ...envArgs, ...specArgs };\n }\n }\n\n const testFilter: TestFilter = {};\n if (input.testName) testFilter.testName = input.testName;\n if (input.testPattern) testFilter.testPattern = input.testPattern;\n if (input.onlyMarked) testFilter.onlyMarked = input.onlyMarked;\n if (input.describeFilter) testFilter.describeFilter = input.describeFilter;\n\n const sessionId = input.automationId ?? `ext-spec-${Date.now()}`;\n\n const shouldRecord = !!input.videoDir;\n\n if (shouldRecord) {\n await this.extensionPageProxy.startRecording();\n }\n\n let result: ExtensionSpecExecutionResult;\n try {\n result = await this.extensionSpecRunner.executeSpec({\n specPath: input.specPath,\n sessionId,\n testFilter: Object.keys(testFilter).length > 0 ? testFilter : undefined,\n specArgs: Object.keys(specArgs).length > 0 ? specArgs : undefined,\n });\n } finally {\n if (shouldRecord && input.videoDir) {\n try {\n const videoBase64 = await this.extensionPageProxy.stopRecording();\n if (videoBase64) {\n mkdirSync(input.videoDir, { recursive: true });\n const videoPath = join(input.videoDir, `recording-${sessionId}.webm`);\n writeFileSync(videoPath, Buffer.from(videoBase64, 'base64'));\n result!.videoPath = videoPath;\n }\n } catch {\n // Recording cleanup failure should not mask spec errors\n }\n }\n }\n\n return this.formatExtensionResult(result);\n }\n\n private async executeBasic(input: RunSpecToolInput, hooksPath?: string | null): Promise<CallToolResult> {\n const result = await this.automationRunner.runSpec({\n specPath: input.specPath,\n automationId: input.automationId,\n browserId: input.browserId,\n pageId: input.pageId,\n keepBrowserOpen: input.keepBrowserOpen ?? false,\n recordVideo: input.videoDir ? { dir: input.videoDir, size: DEFAULT_VIDEO_SIZE } : undefined,\n browserOptions: {\n browserType: input.browserType,\n headless: input.headless,\n baseURL: input.baseURL,\n },\n hooksPath: hooksPath ?? undefined,\n });\n\n return this.formatResult(result, input.keepBrowserOpen ?? false);\n }\n\n private async executeEnhanced(input: RunSpecToolInput, hooksPath?: string | null): Promise<CallToolResult> {\n let specArgs = input.specArgs ?? {};\n\n if (input.loadArgsFromEnv) {\n const metadata = await this.specMetadataService.extractSpecMetadata(input.specPath);\n if (metadata.argsSchema && metadata.envPrefix) {\n const envArgs = this.specMetadataService.parseArgsFromEnv(metadata.argsSchema, metadata.envPrefix);\n specArgs = { ...envArgs, ...specArgs };\n }\n }\n\n const testFilter: TestFilter = {};\n if (input.testName) testFilter.testName = input.testName;\n if (input.testPattern) testFilter.testPattern = input.testPattern;\n if (input.onlyMarked) testFilter.onlyMarked = input.onlyMarked;\n if (input.describeFilter) testFilter.describeFilter = input.describeFilter;\n\n const result = await this.automationRunner.runSpecEnhanced({\n specPath: input.specPath,\n automationId: input.automationId,\n browserId: input.browserId,\n pageId: input.pageId,\n keepBrowserOpen: input.keepBrowserOpen ?? false,\n recordVideo: input.videoDir ? { dir: input.videoDir, size: DEFAULT_VIDEO_SIZE } : undefined,\n browserOptions: {\n browserType: input.browserType,\n headless: input.headless,\n baseURL: input.baseURL,\n },\n testFilter: Object.keys(testFilter).length > 0 ? testFilter : undefined,\n specArgs: Object.keys(specArgs).length > 0 ? specArgs : undefined,\n hooksPath: hooksPath ?? undefined,\n });\n\n return this.formatResult(result, input.keepBrowserOpen ?? false);\n }\n\n private formatResult(\n result: Awaited<ReturnType<IAutomationRunner['runSpec']>>,\n keepBrowserOpen: boolean,\n ): CallToolResult {\n const message = keepBrowserOpen ? 'Spec completed. Browser kept open for further interaction.' : 'Spec completed.';\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n automationId: result.automationId,\n status: result.status,\n pageIds: result.pageIds,\n browserId: result.browserId,\n specResult: result.specResult,\n message,\n },\n null,\n 2,\n ),\n },\n ],\n };\n }\n\n private formatExtensionResult(result: ExtensionSpecExecutionResult): CallToolResult {\n const message = result.success\n ? `Spec completed: ${result.passed}/${result.totalTests} tests passed`\n : `Spec failed: ${result.failed}/${result.totalTests} tests failed`;\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n sessionId: result.sessionId,\n status: result.success ? 'completed' : 'failed',\n totalTests: result.totalTests,\n passed: result.passed,\n failed: result.failed,\n duration: result.duration,\n handoffOccurred: result.handoffOccurred,\n testResults: result.testResults,\n videoPath: result.videoPath,\n message,\n },\n null,\n 2,\n ),\n },\n ],\n isError: !result.success,\n };\n }\n}\n","import 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { IProfileService } from '../services/ProfileService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\nexport interface SaveProfileStateToolInput {\n profileName: string;\n browserId?: string;\n}\n\n@injectable()\nexport class SaveProfileStateTool implements Tool<SaveProfileStateToolInput> {\n static readonly TOOL_NAME = 'browser_save_profile_state';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.ProfileService) private readonly profileService: IProfileService,\n @inject(PLAYWRIGHT_TYPES.BrowserService) private readonly browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ExtensionPageProxy) private readonly extensionPageProxy: IExtensionPageProxy,\n ) {}\n\n getDefinition(): ToolDefinition {\n return {\n name: SaveProfileStateTool.TOOL_NAME,\n description: 'Saves the current browser state to the named profile for reuse across future launches.',\n inputSchema: {\n type: 'object',\n properties: {\n profileName: { type: 'string', description: 'Profile to save state into' },\n browserId: { type: 'string', description: 'Browser to read state from' },\n },\n required: ['profileName'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: SaveProfileStateToolInput): Promise<CallToolResult> {\n try {\n const browser = input.browserId\n ? this.browserService.getBrowser(input.browserId)\n : this.browserService.getBrowserByProfile(input.profileName);\n if (!browser) {\n throw new Error(`Browser for profile \"${input.profileName}\" not found`);\n }\n\n if (browser.context) {\n const storageState = await browser.context.storageState();\n await this.profileService.saveStorageState(input.profileName, storageState as Record<string, unknown>);\n } else {\n const pageId = browser.currentPageId ?? [...browser.pageIds][0];\n if (!pageId) {\n throw new Error(`Browser \"${browser.id}\" has no active page`);\n }\n\n this.extensionPageProxy.setTarget(pageId, browser.id);\n const storageState = await this.extensionPageProxy.getStorageState();\n await this.profileService.saveStorageState(input.profileName, storageState);\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ profileName: input.profileName, browserId: browser.id, saved: true }, null, 2),\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n };\n }\n }\n}\n","/**\n * ScreenshotTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n * - Saves screenshots to temp directory with returned path\n */\n\nimport 'reflect-metadata/lite';\nimport { randomUUID } from 'node:crypto';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface ScreenshotClip {\n /** X coordinate of the top-left corner */\n x: number;\n /** Y coordinate of the top-left corner */\n y: number;\n /** Width of the clipping region */\n width: number;\n /** Height of the clipping region */\n height: number;\n}\n\nexport interface ScreenshotToolInput extends PageTargetInput {\n /** Capture full page scrollable area */\n fullPage?: boolean;\n /** CSS selector for element to screenshot */\n selector?: string;\n /** Clip region to capture (x, y, width, height in pixels) */\n clip?: ScreenshotClip;\n /** Image format */\n type?: 'png' | 'jpeg';\n /** Quality for jpeg (0-100) */\n quality?: number;\n /** Omit background for transparent PNG */\n omitBackground?: boolean;\n /** Custom filename (without extension) */\n filename?: string;\n}\n\n@injectable()\nexport class ScreenshotTool extends BaseTool<ScreenshotToolInput> {\n static readonly TOOL_NAME = 'browser_screenshot';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: ScreenshotTool.TOOL_NAME,\n description:\n 'Captures a screenshot of the page or specific element. Saves to temp directory and returns the file path.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n fullPage: { type: 'boolean', description: 'Capture full page scrollable area', default: false },\n selector: { type: 'string', description: 'CSS selector for element to screenshot' },\n clip: {\n type: 'object',\n description: 'Clip region to capture by coordinates (cannot be used with selector)',\n properties: {\n x: { type: 'number', description: 'X coordinate of top-left corner', minimum: 0 },\n y: { type: 'number', description: 'Y coordinate of top-left corner', minimum: 0 },\n width: { type: 'number', description: 'Width of clipping region', minimum: 1 },\n height: { type: 'number', description: 'Height of clipping region', minimum: 1 },\n },\n required: ['x', 'y', 'width', 'height'],\n additionalProperties: false,\n },\n type: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format', default: 'png' },\n quality: { type: 'number', description: 'Quality for jpeg format (0-100)', minimum: 0, maximum: 100 },\n omitBackground: { type: 'boolean', description: 'Omit background for transparent PNG', default: false },\n filename: { type: 'string', description: 'Custom filename without extension' },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: ScreenshotToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n ScreenshotTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n // Validate that clip and selector cannot be used together\n if (input.clip && input.selector) {\n throw new Error('Cannot use both clip and selector options together');\n }\n\n const imageType = input.type ?? 'png';\n const filename = input.filename ?? `screenshot-${randomUUID()}`;\n const filePath = join(tmpdir(), `${filename}.${imageType}`);\n\n if (input.selector) {\n const element = await page.$(input.selector);\n if (!element) {\n throw new Error(`Element not found: ${input.selector}`);\n }\n await element.screenshot({\n path: filePath,\n type: imageType,\n omitBackground: input.omitBackground ?? false,\n quality: imageType === 'jpeg' ? input.quality : undefined,\n });\n } else {\n await page.screenshot({\n path: filePath,\n type: imageType,\n fullPage: input.fullPage ?? false,\n omitBackground: input.omitBackground ?? false,\n quality: imageType === 'jpeg' ? input.quality : undefined,\n clip: input.clip,\n });\n }\n\n return this.success(`Screenshot saved to: ${filePath}`);\n },\n );\n }\n}\n","/**\n * SelectPageTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation to BrowserService\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Return CallToolResult with content array\n * - Handle errors with isError flag\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n * - Missing input validation\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface SelectPageToolInput {\n /** Page ID to set as current */\n pageId: string;\n}\n\n@injectable()\nexport class SelectPageTool extends BaseTool<SelectPageToolInput> {\n static readonly TOOL_NAME = 'browser_select_page';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: SelectPageTool.TOOL_NAME,\n description:\n 'Switches the active page for a browser. Updates the current page ID so subsequent operations use this page by default.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Page ID to set as current',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: SelectPageToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.pageRegistry.get(input.pageId);\n if (!pageEntry) {\n return this.error(`Page \"${input.pageId}\" not found`);\n }\n\n const { browserId } = pageEntry;\n if (pageEntry.mode === 'extension' || pageEntry.mode === 'vm') {\n const result = await this.executeWithMode(\n SelectPageTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => this.error(`Page \"${input.pageId}\" is not in extension mode`),\n );\n\n if (result.isError) {\n return result;\n }\n }\n\n this.browserService.setCurrentPage(browserId, input.pageId);\n this.browserService.recordBrowserActivity(browserId, input.pageId);\n\n await this.pageRegistry.updateMetadata(input.pageId);\n const updated = this.pageRegistry.get(input.pageId);\n\n return this.successJson({\n browserId,\n pageId: input.pageId,\n url: updated?.url ?? pageEntry.url,\n title: updated?.title ?? pageEntry.title,\n message: `Page \"${input.pageId}\" is now the active page for browser \"${browserId}\"`,\n });\n });\n }\n}\n","/**\n * SelectTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface SelectToolInput extends ElementSelector, PageTargetInput {\n /** Option value(s) to select */\n values?: string[];\n /** Option label(s) to select */\n labels?: string[];\n /** Option index(es) to select (0-based) */\n indexes?: number[];\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class SelectTool extends BaseTool<SelectToolInput> {\n static readonly TOOL_NAME = 'browser_select';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: SelectTool.TOOL_NAME,\n description: 'Selects option(s) from a dropdown/select element. Can select by value, label, or index.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n values: { type: 'array', items: { type: 'string' }, description: 'Option value(s) to select' },\n labels: { type: 'array', items: { type: 'string' }, description: 'Option label(s) to select' },\n indexes: { type: 'array', items: { type: 'number' }, description: 'Option index(es) to select' },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: SelectToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n SelectTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const locator = await this.elementLocator.locate(page, input);\n\n let options: { value?: string; label?: string; index?: number }[] = [];\n\n if (input.values) {\n options = input.values.map((value) => ({ value }));\n } else if (input.labels) {\n options = input.labels.map((label) => ({ label }));\n } else if (input.indexes) {\n options = input.indexes.map((index) => ({ index }));\n }\n\n const selected = await locator.selectOption(options, { timeout: input.timeout });\n return this.success(`Selected ${selected.length} option(s): ${selected.join(', ')}`);\n },\n );\n }\n}\n","/**\n * SnapshotTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n * - Returns accessibility tree snapshot preferred for LLM processing\n */\n\nimport 'reflect-metadata/lite';\nimport { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\n/** Default threshold in bytes for when to save snapshot to file vs return inline */\nconst DEFAULT_SIZE_THRESHOLD = 10000;\n\nexport interface SnapshotToolInput extends PageTargetInput {\n /** Root element selector for partial snapshot (defaults to 'body') */\n root?: string;\n /** Size threshold in bytes; snapshots larger than this are saved to file (default: 10000) */\n sizeThreshold?: number;\n}\n\n@injectable()\nexport class SnapshotTool extends BaseTool<SnapshotToolInput> {\n static readonly TOOL_NAME = 'browser_snapshot';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: SnapshotTool.TOOL_NAME,\n description:\n 'Returns the accessibility tree snapshot of the page. Preferred for LLM processing as it provides structured, semantic information about page content.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n root: { type: 'string', description: 'CSS selector for root element to snapshot (defaults to body)' },\n sizeThreshold: {\n type: 'number',\n description: 'Size threshold in bytes; snapshots larger than this are saved to file (default: 10000)',\n default: 10000,\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: SnapshotToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n SnapshotTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n // Use locator-based ariaSnapshot for type safety\n const rootSelector = input.root ?? 'body';\n const locator = page.locator(rootSelector);\n\n // Check if root element exists\n const count = await locator.count();\n if (count === 0) {\n throw new Error(`Root element not found: ${rootSelector}`);\n }\n\n // Get aria snapshot from the locator\n const snapshot = await locator.ariaSnapshot();\n\n if (!snapshot) {\n return this.success('No accessibility tree available for this page');\n }\n\n const sizeThreshold = input.sizeThreshold ?? DEFAULT_SIZE_THRESHOLD;\n const snapshotSize = Buffer.byteLength(snapshot, 'utf-8');\n\n // Return snapshot directly if under threshold\n if (snapshotSize <= sizeThreshold) {\n return this.success(snapshot);\n }\n\n // Save large snapshot to temp file\n const filename = `snapshot-${randomUUID()}.yaml`;\n const filePath = join(tmpdir(), filename);\n await writeFile(filePath, snapshot, 'utf-8');\n\n return this.success(`Snapshot saved to: ${filePath}`);\n },\n );\n }\n}\n","/**\n * StartTraceTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface StartTraceToolInput extends PageTargetInput {\n /** Name for the trace (used in trace filename) */\n name?: string;\n /** Whether to capture screenshots during tracing (default: true) */\n screenshots?: boolean;\n /** Whether to capture snapshots during tracing (default: true) */\n snapshots?: boolean;\n /** Whether to include source files in the trace (default: false) */\n sources?: boolean;\n}\n\n@injectable()\nexport class StartTraceTool extends BaseTool<StartTraceToolInput> {\n static readonly TOOL_NAME = 'browser_start_trace';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: StartTraceTool.TOOL_NAME,\n description:\n 'Starts Playwright performance tracing with configurable options for screenshots, snapshots, and sources. Use browser_stop_trace to stop and save the trace.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n name: {\n type: 'string',\n description: 'Name for the trace (used in trace filename)',\n },\n screenshots: {\n type: 'boolean',\n description: 'Whether to capture screenshots during tracing (default: true)',\n },\n snapshots: {\n type: 'boolean',\n description: 'Whether to capture snapshots during tracing (default: true)',\n },\n sources: {\n type: 'boolean',\n description: 'Whether to include source files in the trace (default: false)',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: StartTraceToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n const context = pageEntry.context;\n\n if (!context) {\n return this.error(`Page \"${input.pageId}\" is in extension mode and cannot start tracing`);\n }\n\n const { name, screenshots = true, snapshots = true, sources = false } = input;\n\n await context.tracing.start({\n name,\n screenshots,\n snapshots,\n sources,\n });\n\n return this.successJson({\n success: true,\n message: 'Tracing started',\n pageId: pageEntry.id,\n options: {\n name: name ?? null,\n screenshots,\n snapshots,\n sources,\n },\n });\n });\n }\n}\n","import 'reflect-metadata/lite';\nimport path from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface StartRecordingToolInput extends PageTargetInput {\n /** Optional output path for the recording artifact. Defaults to a temp file if omitted. */\n path?: string;\n}\n\n@injectable()\nexport class StartRecordingTool extends BaseTool<StartRecordingToolInput> {\n static readonly TOOL_NAME = 'browser_start_recording';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: StartRecordingTool.TOOL_NAME,\n description:\n 'Starts recording an existing extension/vm browser page and saves the artifact to the provided path or a temporary .webm file.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'The page ID to record.',\n },\n path: {\n type: 'string',\n description:\n 'Optional absolute output path for the .webm file. If omitted, browse-tool creates a temporary recording path.',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: StartRecordingToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n if (pageEntry.mode !== 'extension' && pageEntry.mode !== 'vm') {\n return this.error(`Page \"${input.pageId}\" is in \"${pageEntry.mode}\" mode and cannot use extension recording`);\n }\n\n if (input.path) {\n if (!path.isAbsolute(input.path)) {\n return this.error('Recording path must be an absolute path (e.g., /Users/me/project/recording.webm)');\n }\n if (path.extname(input.path).toLowerCase() !== '.webm') {\n return this.error('Recording path must end with .webm');\n }\n }\n\n const result = await this.browserService.startPageRecording(pageEntry.browserId, pageEntry.id, input.path);\n\n return this.successJson({\n started: true,\n browserId: pageEntry.browserId,\n pageId: pageEntry.id,\n outputPath: result.outputPath,\n });\n });\n }\n}\n","/**\n * StopTraceTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution\n * - JSON Schema validation for inputs\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with browser_ prefix\n * - Return CallToolResult with content array\n * - Handle errors with safeExecute wrapper\n *\n * AVOID:\n * - Complex business logic in execute method\n * - Unhandled promise rejections\n */\n\nimport 'reflect-metadata/lite';\nimport path from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface StopTraceToolInput extends PageTargetInput {\n /** Path where the trace file should be saved (required) */\n path: string;\n}\n\n@injectable()\nexport class StopTraceTool extends BaseTool<StopTraceToolInput> {\n static readonly TOOL_NAME = 'browser_stop_trace';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: StopTraceTool.TOOL_NAME,\n description:\n 'Stops Playwright performance tracing and saves the trace file to a specified path. The trace can be viewed using Playwright Trace Viewer.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'Optional page ID to override browser current page',\n },\n path: {\n type: 'string',\n description:\n 'Absolute path where the trace file should be saved (e.g., \"/Users/me/project/traces/trace.zip\"). Must end with .zip extension.',\n },\n },\n required: ['pageId', 'path'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: StopTraceToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n const context = pageEntry.context;\n\n if (!context) {\n return this.error(`Page \"${input.pageId}\" is in extension mode and cannot stop tracing`);\n }\n\n const tracePath = input.path;\n\n if (!path.isAbsolute(tracePath)) {\n return this.error('Trace path must be an absolute path (e.g., /Users/me/project/traces/trace.zip)');\n }\n\n // Validate path ends with .zip\n if (!tracePath.endsWith('.zip')) {\n return this.error('Trace path must end with .zip extension');\n }\n\n await context.tracing.stop({ path: tracePath });\n\n return this.successJson({\n success: true,\n message: 'Tracing stopped and saved',\n pageId: pageEntry.id,\n tracePath,\n viewCommand: `npx playwright show-trace ${tracePath}`,\n });\n });\n }\n}\n","import 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface StopRecordingToolInput extends PageTargetInput {\n /** Include base64 video payload in the result. Disabled by default. */\n includeBase64?: boolean;\n}\n\n@injectable()\nexport class StopRecordingTool extends BaseTool<StopRecordingToolInput> {\n static readonly TOOL_NAME = 'browser_stop_recording';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: StopRecordingTool.TOOL_NAME,\n description:\n 'Stops an active extension/vm browser recording and returns the saved .webm path with optional base64 payload.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: {\n type: 'string',\n description: 'The page ID whose recording should be stopped.',\n },\n includeBase64: {\n type: 'boolean',\n description: 'Whether to include the base64-encoded WebM payload in the response.',\n },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: StopRecordingToolInput): Promise<CallToolResult> {\n return this.safeExecute(async () => {\n const pageEntry = this.resolvePageEntry(input.pageId);\n if (pageEntry.mode !== 'extension' && pageEntry.mode !== 'vm') {\n return this.error(`Page \"${input.pageId}\" is in \"${pageEntry.mode}\" mode and cannot use extension recording`);\n }\n\n const result = await this.browserService.stopPageRecording(pageEntry.browserId, pageEntry.id, {\n includeBase64: input.includeBase64,\n });\n\n return this.successJson({\n stopped: true,\n browserId: pageEntry.browserId,\n pageId: pageEntry.id,\n outputPath: result.outputPath,\n fileSizeBytes: result.fileSizeBytes,\n ...(result.videoBase64 ? { videoBase64: result.videoBase64 } : {}),\n });\n });\n }\n}\n","/**\n * TypeTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface TypeToolInput extends ElementSelector, PageTargetInput {\n /** The text to type character by character */\n text: string;\n /** Delay between key presses in milliseconds */\n delay?: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class TypeTool extends BaseTool<TypeToolInput> {\n static readonly TOOL_NAME = 'browser_type';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: TypeTool.TOOL_NAME,\n description:\n 'Types text into an element character by character, simulating real keyboard input. Does not clear existing content. Use browser_fill for replacing content.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n text: { type: 'string', description: 'The text to type character by character' },\n delay: { type: 'number', description: 'Delay between key presses in ms', default: 0 },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId', 'text'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: TypeToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n TypeTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const locator = await this.elementLocator.locate(page, {\n selector: input.selector,\n xpath: input.xpath,\n uid: input.uid,\n frame: input.frame,\n });\n await locator.pressSequentially(input.text, { delay: input.delay, timeout: input.timeout });\n return this.success(`Typed: \"${input.text}\"`);\n },\n );\n }\n}\n","/**\n * UploadFileTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - JSON Schema validation for inputs\n * - Session-based page resolution with optional pageId override\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ElementSelector, IElementLocatorService } from '../services/ElementLocatorService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface UploadFileToolInput extends ElementSelector, PageTargetInput {\n /** File path(s) to upload */\n files: string | string[];\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class UploadFileTool extends BaseTool<UploadFileToolInput> {\n static readonly TOOL_NAME = 'browser_upload_file';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n @inject(PLAYWRIGHT_TYPES.ElementLocatorService) private readonly elementLocator: IElementLocatorService,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: UploadFileTool.TOOL_NAME,\n description: 'Uploads file(s) to a file input element.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n selector: { type: 'string', description: 'CSS selector' },\n xpath: { type: 'string', description: 'XPath expression' },\n text: { type: 'string', description: 'Text content to match' },\n uid: { type: 'string', description: 'Accessibility snapshot UID' },\n frame: { type: 'string', description: 'Frame selector' },\n files: {\n oneOf: [\n { type: 'string', description: 'Single file path to upload' },\n { type: 'array', items: { type: 'string' }, description: 'Multiple file paths to upload' },\n ],\n description: 'File path(s) to upload',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId', 'files'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: UploadFileToolInput): Promise<CallToolResult> {\n return this.executeWithMode(\n UploadFileTool.TOOL_NAME,\n input.pageId,\n input as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n const locator = await this.elementLocator.locate(page, input);\n await locator.setInputFiles(input.files, { timeout: input.timeout });\n\n const fileCount = Array.isArray(input.files) ? input.files.length : 1;\n return this.success(`Uploaded ${fileCount} file(s) successfully`);\n },\n );\n }\n}\n","/**\n * WaitForTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Session-based page resolution with optional pageId override\n * - JSON Schema validation for inputs\n * - Supports multiple wait conditions: element, text, timeout, load state\n */\n\nimport 'reflect-metadata/lite';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { inject, injectable } from 'inversify';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { ToolExecutor } from '../services/ToolExecutor.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { BaseTool, type PageTargetInput } from './BaseTool.js';\n\nexport interface WaitForToolInput extends PageTargetInput {\n /** Type of wait condition */\n type?: 'element' | 'text' | 'timeout' | 'loadState';\n /** CSS selector for element wait */\n selector?: string;\n /** Text to wait for (exact match or regex pattern) */\n text?: string;\n /** Duration in milliseconds for timeout wait */\n duration?: number;\n /** Load state to wait for */\n state?: 'load' | 'domcontentloaded' | 'networkidle';\n /** Element state for element wait */\n elementState?: 'attached' | 'detached' | 'visible' | 'hidden';\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n@injectable()\nexport class WaitForTool extends BaseTool<WaitForToolInput> {\n static readonly TOOL_NAME = 'browser_wait_for';\n\n constructor(\n @inject(PLAYWRIGHT_TYPES.PageRegistry) pageRegistry: IPageRegistry,\n @inject(PLAYWRIGHT_TYPES.BrowserService) browserService: IBrowserService,\n @inject(PLAYWRIGHT_TYPES.ToolExecutor) toolExecutor: ToolExecutor,\n ) {\n super(pageRegistry, browserService, toolExecutor);\n }\n\n getDefinition(): ToolDefinition {\n return {\n name: WaitForTool.TOOL_NAME,\n description:\n 'Waits for a condition to be met: element visibility, text appearance, timeout duration, or page load state.',\n inputSchema: {\n type: 'object',\n properties: {\n pageId: { type: 'string', description: 'Page ID to operate on' },\n type: {\n type: 'string',\n enum: ['element', 'text', 'timeout', 'loadState'],\n description: 'Type of wait condition',\n },\n selector: { type: 'string', description: 'CSS selector for element wait' },\n text: { type: 'string', description: 'Text to wait for (supports regex patterns)' },\n duration: { type: 'number', description: 'Duration in milliseconds for timeout wait' },\n state: {\n type: 'string',\n enum: ['load', 'domcontentloaded', 'networkidle'],\n description: 'Load state to wait for',\n },\n elementState: {\n type: 'string',\n enum: ['attached', 'detached', 'visible', 'hidden'],\n description: 'Element state to wait for',\n default: 'visible',\n },\n timeout: { type: 'number', description: 'Timeout in milliseconds', default: DEFAULT_TOOL_TIMEOUT_MS },\n },\n required: ['pageId'],\n additionalProperties: false,\n },\n };\n }\n\n async execute(input: WaitForToolInput): Promise<CallToolResult> {\n const waitType =\n input.type ??\n (input.text\n ? 'text'\n : input.selector || input.elementState\n ? 'element'\n : input.duration !== undefined\n ? 'timeout'\n : input.state\n ? 'loadState'\n : undefined);\n\n if (!waitType) {\n return this.error('Unable to infer wait type. Provide \"type\" or one of selector, text, duration, or state.');\n }\n\n // Handle simple timeout wait directly - no need to delegate to extension\n if (waitType === 'timeout') {\n const duration = input.duration ?? 1000;\n await new Promise((resolve) => setTimeout(resolve, duration));\n return this.success(`Waited for ${duration}ms`);\n }\n\n return this.executeWithMode(\n WaitForTool.TOOL_NAME,\n input.pageId,\n { ...input, type: waitType } as unknown as Record<string, unknown>,\n async () => {\n const page = this.resolvePage(input.pageId);\n\n switch (waitType) {\n case 'element': {\n if (!input.selector) {\n throw new Error('selector is required for element wait');\n }\n const locator = page.locator(input.selector);\n await locator.waitFor({\n state: input.elementState ?? 'visible',\n timeout: input.timeout,\n });\n return this.success(`Element \"${input.selector}\" is ${input.elementState ?? 'visible'}`);\n }\n\n case 'text': {\n if (!input.text) {\n throw new Error('text is required for text wait');\n }\n const textLocator = page.getByText(input.text);\n await textLocator.waitFor({\n state: 'visible',\n timeout: input.timeout,\n });\n return this.success(`Text \"${input.text}\" appeared on page`);\n }\n\n case 'loadState': {\n const state = input.state ?? 'load';\n await page.waitForLoadState(state, { timeout: input.timeout });\n return this.success(`Page reached \"${state}\" state`);\n }\n\n default:\n throw new Error(`Unknown wait type: ${waitType}`);\n }\n },\n );\n }\n}\n","/**\n * Container Configuration\n *\n * DESIGN PATTERNS:\n * - Two separate container configurations to prevent accidental misuse\n * - MCP Container: Minimal, stateless proxy services only\n * - HTTP Container: Full services for browser management and tools\n *\n * ARCHITECTURE:\n * - MCP server is stateless, proxies all calls to HTTP server\n * - HTTP server holds all state: browsers, pages, profiles\n * - This separation prevents accidental use of local BrowserService in MCP context\n */\n\nimport 'reflect-metadata/lite';\nimport { ProcessRegistryService } from '@agimon-ai/foundation-process-registry';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { AutomationRunner } from '../services/AutomationRunner.js';\nimport { BrowserLockManager } from '../services/BrowserLockManager.js';\nimport { BrowserService } from '../services/BrowserService.js';\nimport { ChromeForTestingService } from '../services/ChromeForTestingService.js';\nimport { CodeSnippetService } from '../services/CodeSnippetService.js';\nimport { ElementLocatorService } from '../services/ElementLocatorService.js';\nimport { ExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport { ExtensionSessionRegistry } from '../services/ExtensionSessionRegistry.js';\nimport { ExtensionSpecRunner } from '../services/ExtensionSpecRunner.js';\nimport { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { ExtensionToolDelegator } from '../services/ExtensionToolDelegator.js';\nimport { HttpBrowserClient } from '../services/HttpBrowserClient.js';\nimport { HttpServerHealthCheck } from '../services/HttpServerHealthCheck.js';\nimport { HttpServerManager } from '../services/HttpServerManager.js';\nimport { IdleCleanupService } from '../services/IdleCleanupService.js';\nimport { McpSessionTracker } from '../services/McpSessionTracker.js';\nimport { PageMonitorService } from '../services/PageMonitorService.js';\nimport { PageRegistry } from '../services/PageRegistry.js';\nimport { PauseController } from '../services/PauseController.js';\nimport { ProfileService } from '../services/ProfileService.js';\nimport { RemoteToolExecutor } from '../services/RemoteToolExecutor.js';\nimport { SetupRunner } from '../services/SetupRunner.js';\nimport { SpecBundlerService } from '../services/SpecBundlerService.js';\nimport { SpecDiscoveryService } from '../services/SpecDiscoveryService.js';\nimport { SpecMetadataService } from '../services/SpecMetadataService.js';\nimport { SpecRunner } from '../services/SpecRunner.js';\nimport { StealthLauncher } from '../services/StealthLauncher.js';\nimport { TelemetryService } from '../services/TelemetryService.js';\nimport { ToolExecutor } from '../services/ToolExecutor.js';\nimport { WebServerManager } from '../services/WebServerManager.js';\nimport { WebSocketHub } from '../services/WebSocketHub.js';\nimport { ClickTool } from '../tools/ClickTool.js';\nimport { CloseBrowserTool } from '../tools/CloseBrowserTool.js';\nimport { ClosePageTool } from '../tools/ClosePageTool.js';\nimport { CreateProfileTool } from '../tools/CreateProfileTool.js';\nimport { DeleteProfileTool } from '../tools/DeleteProfileTool.js';\nimport { DiscoverSpecsTool } from '../tools/DiscoverSpecsTool.js';\nimport { DragTool } from '../tools/DragTool.js';\nimport { EmulateTool } from '../tools/EmulateTool.js';\nimport { EvaluateScriptTool } from '../tools/EvaluateScriptTool.js';\nimport { ExpectTool } from '../tools/ExpectTool.js';\nimport { FillTool } from '../tools/FillTool.js';\nimport { GetNetworkRequestTool } from '../tools/GetNetworkRequestTool.js';\nimport { GoBackTool } from '../tools/GoBackTool.js';\nimport { GoForwardTool } from '../tools/GoForwardTool.js';\nimport { HandleDialogTool } from '../tools/HandleDialogTool.js';\nimport { HoverTool } from '../tools/HoverTool.js';\nimport { LaunchBrowserTool } from '../tools/LaunchBrowserTool.js';\nimport { ListConsoleMessagesTool } from '../tools/ListConsoleMessagesTool.js';\nimport { ListNetworkRequestsTool } from '../tools/ListNetworkRequestsTool.js';\nimport { ListPagesTool } from '../tools/ListPagesTool.js';\nimport { ListProfilesTool } from '../tools/ListProfilesTool.js';\nimport { ListSnippetsTool } from '../tools/ListSnippetsTool.js';\nimport { NavigateTool } from '../tools/NavigateTool.js';\nimport { NewPageTool } from '../tools/NewPageTool.js';\nimport { PdfTool } from '../tools/PdfTool.js';\nimport { PressKeyTool } from '../tools/PressKeyTool.js';\nimport { ReloadTool } from '../tools/ReloadTool.js';\nimport { ResizePageTool } from '../tools/ResizePageTool.js';\nimport { RunCodeTool } from '../tools/RunCodeTool.js';\nimport { RunSpecTool } from '../tools/RunSpecTool.js';\nimport { SaveProfileStateTool } from '../tools/SaveProfileStateTool.js';\nimport { ScreenshotTool } from '../tools/ScreenshotTool.js';\nimport { SelectPageTool } from '../tools/SelectPageTool.js';\nimport { SelectTool } from '../tools/SelectTool.js';\nimport { SnapshotTool } from '../tools/SnapshotTool.js';\nimport { StartTraceTool } from '../tools/StartTraceTool.js';\nimport { StartRecordingTool } from '../tools/StartRecordingTool.js';\nimport { StopTraceTool } from '../tools/StopTraceTool.js';\nimport { StopRecordingTool } from '../tools/StopRecordingTool.js';\nimport { TypeTool } from '../tools/TypeTool.js';\nimport { UploadFileTool } from '../tools/UploadFileTool.js';\nimport { WaitForTool } from '../tools/WaitForTool.js';\n\n// ============================================================================\n// MCP Client Module (Stateless Proxy)\n// ============================================================================\n\n/**\n * Minimal services for MCP proxy mode.\n * MCP server is stateless - it only needs services to discover and proxy to HTTP server.\n *\n * INCLUDES:\n * - HttpServerHealthCheck: Check if HTTP server is running\n * - HttpServerManager: Spawn/discover HTTP server\n * - McpSessionTracker: Track resources created in this session for cleanup\n *\n * EXCLUDES (intentionally):\n * - BrowserService, PageRegistry, ProfileService (live in HTTP server)\n * - All Tools (executed via HTTP server's /execute endpoint)\n */\nexport const mcpClientModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n options\n .bind(PLAYWRIGHT_TYPES.PortRegistryService)\n .toDynamicValue(() => new PortRegistryService(process.env.PORT_REGISTRY_PATH))\n .inSingletonScope();\n options\n .bind(PLAYWRIGHT_TYPES.ProcessRegistryService)\n .toDynamicValue(() => new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH))\n .inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpServerHealthCheck).to(HttpServerHealthCheck).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpServerManager).to(HttpServerManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.McpSessionTracker).to(McpSessionTracker).inSingletonScope();\n});\n\n// ============================================================================\n// HTTP Server Module (Full State)\n// ============================================================================\n\n/**\n * Full services for HTTP server - the singleton that holds all browser state.\n * All browser operations, page management, and tool execution happen here.\n */\nexport const httpServerServicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n // Core browser management\n options.bind(PLAYWRIGHT_TYPES.ProfileService).to(ProfileService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PageRegistry).to(PageRegistry).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.BrowserService).to(BrowserService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.BrowserLockManager).to(BrowserLockManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ChromeForTestingService).to(ChromeForTestingService).inSingletonScope();\n\n // Page and element services\n options.bind(PLAYWRIGHT_TYPES.ElementLocatorService).to(ElementLocatorService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PageMonitorService).to(PageMonitorService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PauseController).to(PauseController).inSingletonScope();\n\n // Spec/automation services\n options.bind(PLAYWRIGHT_TYPES.SpecBundlerService).to(SpecBundlerService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecDiscoveryService).to(SpecDiscoveryService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecMetadataService).to(SpecMetadataService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SetupRunner).to(SetupRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.WebServerManager).to(WebServerManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecRunner).to(SpecRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.AutomationRunner).to(AutomationRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.TelemetryService).to(TelemetryService).inSingletonScope();\n options\n .bind(PLAYWRIGHT_TYPES.CodeSnippetService)\n .toDynamicValue(() => new CodeSnippetService(process.env.BROWSE_TOOL_SNIPPETS_DIR))\n .inSingletonScope();\n\n // Extension services (for extension mode browsers)\n options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ToolExecutor).to(ToolExecutor).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionSessionRegistry).to(ExtensionSessionRegistry).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionPageProxy).to(ExtensionPageProxy).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionSpecRunner).to(ExtensionSpecRunner).inSingletonScope();\n\n // Stealth mode services\n options.bind(PLAYWRIGHT_TYPES.StealthLauncher).to(StealthLauncher).inSingletonScope();\n\n // HTTP client services (for remote tool execution if needed)\n options.bind(PLAYWRIGHT_TYPES.HttpServerHealthCheck).to(HttpServerHealthCheck).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpBrowserClient).to(HttpBrowserClient).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.RemoteToolExecutor).to(RemoteToolExecutor).inSingletonScope();\n options\n .bind(PLAYWRIGHT_TYPES.ProcessRegistryService)\n .toDynamicValue(() => new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH))\n .inSingletonScope();\n\n // WebSocket services\n options.bind(PLAYWRIGHT_TYPES.WebSocketHub).to(WebSocketHub).inSingletonScope();\n\n // Idle cleanup\n options.bind(PLAYWRIGHT_TYPES.IdleCleanupService).to(IdleCleanupService).inSingletonScope();\n});\n\n/**\n * All tools for HTTP server - executed via /execute endpoint\n */\nexport const httpServerToolsModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n // INPUT tools (8)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ClickTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(FillTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(TypeTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(SelectTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(HoverTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(DragTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(PressKeyTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(UploadFileTool).inSingletonScope();\n // NAVIGATION tools (5)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(NavigateTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(GoBackTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(GoForwardTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ReloadTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(WaitForTool).inSingletonScope();\n // SNAPSHOT tools (3)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(SnapshotTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ScreenshotTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(PdfTool).inSingletonScope();\n // TAB/PAGE tools (4)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ListPagesTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(NewPageTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(SelectPageTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ClosePageTool).inSingletonScope();\n // DIALOG, NETWORK, CONSOLE, SCRIPT, EMULATION tools (7)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(HandleDialogTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ListNetworkRequestsTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(GetNetworkRequestTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ListConsoleMessagesTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(EvaluateScriptTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(EmulateTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ResizePageTool).inSingletonScope();\n // TESTING and TRACING tools (3)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ExpectTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(StartRecordingTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(StopRecordingTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(StartTraceTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(StopTraceTool).inSingletonScope();\n // PROFILE tools (4)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ListProfilesTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(ListSnippetsTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(CreateProfileTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(DeleteProfileTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(SaveProfileStateTool).inSingletonScope();\n // SPEC tools (2)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(RunSpecTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(DiscoverSpecsTool).inSingletonScope();\n // BROWSER LAUNCH/CLOSE tools (2)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(LaunchBrowserTool).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.Tool).to(CloseBrowserTool).inSingletonScope();\n // CODE EXECUTION tools (2)\n options.bind(PLAYWRIGHT_TYPES.Tool).to(RunCodeTool).inSingletonScope();\n});\n\n// ============================================================================\n// Container Factory Functions\n// ============================================================================\n\n/**\n * Creates a minimal container for MCP proxy mode.\n * Use this in mcp-serve command.\n *\n * This container intentionally EXCLUDES BrowserService, PageRegistry, and Tools\n * to prevent accidental local state management in MCP process.\n */\nexport function createMcpContainer(): Container {\n const container = new Container({\n defaultScope: 'Singleton',\n });\n container.load(mcpClientModule);\n return container;\n}\n\n/**\n * Creates a full container for HTTP server mode.\n * Use this in http-serve command.\n *\n * This container includes all services and tools needed for browser management.\n */\nexport function createHttpContainer(): Container {\n const container = new Container({\n defaultScope: 'Singleton',\n });\n container.load(httpServerServicesModule, httpServerToolsModule);\n return container;\n}\n\n// ============================================================================\n// Legacy Exports (Deprecated - use createMcpContainer or createHttpContainer)\n// ============================================================================\n\n/**\n * @deprecated Use createMcpContainer() or createHttpContainer() instead.\n * Kept for backward compatibility during migration.\n */\nexport const servicesModule = new ContainerModule((options: ContainerModuleLoadOptions) => {\n // Load all services from both modules for backward compatibility\n options\n .bind(PLAYWRIGHT_TYPES.PortRegistryService)\n .toDynamicValue(() => new PortRegistryService(process.env.PORT_REGISTRY_PATH))\n .inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ProfileService).to(ProfileService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PageRegistry).to(PageRegistry).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.BrowserService).to(BrowserService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ElementLocatorService).to(ElementLocatorService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PageMonitorService).to(PageMonitorService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.PauseController).to(PauseController).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecBundlerService).to(SpecBundlerService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecDiscoveryService).to(SpecDiscoveryService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecMetadataService).to(SpecMetadataService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SetupRunner).to(SetupRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.WebServerManager).to(WebServerManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.SpecRunner).to(SpecRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.AutomationRunner).to(AutomationRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpServerHealthCheck).to(HttpServerHealthCheck).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpServerManager).to(HttpServerManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.HttpBrowserClient).to(HttpBrowserClient).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.RemoteToolExecutor).to(RemoteToolExecutor).inSingletonScope();\n options\n .bind(PLAYWRIGHT_TYPES.ProcessRegistryService)\n .toDynamicValue(() => new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH))\n .inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ToolExecutor).to(ToolExecutor).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.StealthLauncher).to(StealthLauncher).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.BrowserLockManager).to(BrowserLockManager).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionSessionRegistry).to(ExtensionSessionRegistry).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionPageProxy).to(ExtensionPageProxy).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ExtensionSpecRunner).to(ExtensionSpecRunner).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.McpSessionTracker).to(McpSessionTracker).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.ChromeForTestingService).to(ChromeForTestingService).inSingletonScope();\n options.bind(PLAYWRIGHT_TYPES.IdleCleanupService).to(IdleCleanupService).inSingletonScope();\n});\n\n/**\n * @deprecated Use createHttpContainer() instead.\n */\nexport const toolsModule = httpServerToolsModule;\n\n/**\n * @deprecated Use createMcpContainer() or createHttpContainer() instead.\n */\nexport const container = new Container({\n defaultScope: 'Singleton',\n});\n\ncontainer.load(servicesModule, httpServerToolsModule);\n\n/**\n * @deprecated Use createMcpContainer() or createHttpContainer() instead.\n */\nexport function createContainer(): Container {\n const newContainer = new Container({\n defaultScope: 'Singleton',\n });\n newContainer.load(servicesModule, httpServerToolsModule);\n return newContainer;\n}\n\nexport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\n","/**\n * STDIO Transport\n *\n * DESIGN PATTERNS:\n * - Transport handler pattern implementing TransportHandler interface\n * - Standard I/O based communication for CLI usage\n *\n * CODING STANDARDS:\n * - Initialize server and transport properly\n * - Handle cleanup on shutdown with stop() method\n * - Use async/await for all operations\n *\n * AVOID:\n * - Forgetting to close transport on shutdown\n * - Missing error handling for connection failures\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n/**\n * Error thrown when transport connection fails.\n */\nexport class TransportConnectionError extends Error {\n readonly code = 'TRANSPORT_CONNECTION_ERROR';\n readonly recovery = 'Check that stdio streams are available and try again.';\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'TransportConnectionError';\n }\n}\n\n/**\n * Stdio transport handler for MCP server\n * Used for command-line and direct integrations\n */\nexport class StdioTransportHandler {\n private server: Server;\n private transport: StdioServerTransport | null = null;\n\n constructor(server: Server) {\n this.server = server;\n }\n\n async start(): Promise<void> {\n const transport = new StdioServerTransport();\n try {\n await this.server.connect(transport);\n this.transport = transport;\n console.error('browse-tool MCP server started on stdio');\n } catch (error) {\n // Cleanup partially initialized transport\n try {\n await transport.close();\n } catch {\n // Best-effort cleanup, ignore errors\n }\n const connectionError = new TransportConnectionError('Failed to establish stdio transport connection', {\n cause: error,\n });\n console.error(`[${connectionError.code}] ${connectionError.message}. Recovery: ${connectionError.recovery}`);\n throw connectionError;\n }\n }\n\n async stop(): Promise<void> {\n // NOTE: Browser instances are NOT closed here.\n // Browsers persist in the HTTP server for reuse across MCP sessions.\n // The HTTP server is responsible for browser cleanup on its own shutdown.\n\n if (this.transport) {\n await this.transport.close();\n this.transport = null;\n }\n }\n}\n","import { createServer, type IncomingMessage, type Server as HttpServer, type ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface StreamableHttpSessionContext {\n server: Server;\n onClose?: () => Promise<void> | void;\n}\n\nexport interface StreamableHttpSessionRequest {\n headers: IncomingMessage['headers'];\n}\n\nexport interface StreamableHttpTransportConfig {\n host: string;\n port: number;\n path?: string;\n}\n\ninterface HttpSession {\n server: Server;\n transport: StreamableHTTPServerTransport;\n onClose?: () => Promise<void> | void;\n}\n\n/**\n * Error thrown when transport connection fails.\n */\nexport class TransportConnectionError extends Error {\n readonly code = 'TRANSPORT_CONNECTION_ERROR';\n readonly recovery = 'Check that the HTTP host and port are available and try again.';\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'TransportConnectionError';\n }\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n\n if (chunks.length === 0) {\n return undefined;\n }\n\n const body = Buffer.concat(chunks).toString('utf8').trim();\n if (!body) {\n return undefined;\n }\n\n return JSON.parse(body) as unknown;\n}\n\nfunction writeJson(res: ServerResponse, statusCode: number, payload: unknown): void {\n if (res.headersSent) {\n return;\n }\n\n res.writeHead(statusCode, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(payload));\n}\n\nclass HttpSessionManager {\n private readonly sessions = new Map<string, HttpSession>();\n\n get(sessionId: string): HttpSession | undefined {\n return this.sessions.get(sessionId);\n }\n\n set(sessionId: string, session: HttpSession): void {\n this.sessions.set(sessionId, session);\n }\n\n has(sessionId: string): boolean {\n return this.sessions.has(sessionId);\n }\n\n async delete(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return;\n }\n\n this.sessions.delete(sessionId);\n session.server.close();\n await session.onClose?.();\n }\n\n async clear(): Promise<void> {\n const sessionIds = [...this.sessions.keys()];\n for (const sessionId of sessionIds) {\n await this.delete(sessionId);\n }\n }\n}\n\n/**\n * Streamable HTTP transport handler for MCP server.\n */\nexport class StreamableHttpTransportHandler {\n private readonly sessionFactory: (request: StreamableHttpSessionRequest) => StreamableHttpSessionContext;\n private readonly config: Required<StreamableHttpTransportConfig>;\n private readonly sessionManager = new HttpSessionManager();\n private httpServer: HttpServer | null = null;\n\n constructor(\n sessionFactory: (request: StreamableHttpSessionRequest) => StreamableHttpSessionContext,\n config: StreamableHttpTransportConfig,\n ) {\n this.sessionFactory = sessionFactory;\n this.config = {\n host: config.host,\n port: config.port,\n path: config.path ?? '/mcp',\n };\n }\n\n private async createSession(req: IncomingMessage, parsedBody: unknown): Promise<StreamableHTTPServerTransport> {\n if (!isInitializeRequest(parsedBody)) {\n throw new Error('No valid session ID provided');\n }\n\n const context = this.sessionFactory({ headers: req.headers });\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableJsonResponse: true,\n onsessioninitialized: (sessionId) => {\n this.sessionManager.set(sessionId, {\n server: context.server,\n transport,\n onClose: context.onClose,\n });\n },\n onsessionclosed: async (sessionId) => {\n await this.sessionManager.delete(sessionId);\n },\n });\n\n transport.onclose = () => {\n if (transport.sessionId) {\n void this.sessionManager.delete(transport.sessionId);\n }\n };\n\n await context.server.connect(transport);\n return transport;\n }\n\n private async handleMcpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const sessionIdHeader = req.headers['mcp-session-id'];\n const sessionId = Array.isArray(sessionIdHeader) ? sessionIdHeader[0] : sessionIdHeader;\n\n let parsedBody: unknown;\n if (req.method === 'POST') {\n try {\n parsedBody = await readJsonBody(req);\n } catch (error) {\n writeJson(res, 400, {\n jsonrpc: '2.0',\n error: {\n code: -32700,\n message: error instanceof Error ? error.message : 'Invalid JSON body',\n },\n id: null,\n });\n return;\n }\n }\n\n let transport: StreamableHTTPServerTransport | undefined;\n if (sessionId) {\n transport = this.sessionManager.get(sessionId)?.transport;\n } else if (req.method === 'POST') {\n transport = await this.createSession(req, parsedBody);\n }\n\n if (!transport) {\n writeJson(res, sessionId ? 404 : 400, {\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: sessionId ? 'Session not found' : 'Bad Request: No valid session ID provided',\n },\n id: null,\n });\n return;\n }\n\n await transport.handleRequest(req, res, parsedBody);\n }\n\n async start(): Promise<void> {\n if (this.httpServer) {\n throw new TransportConnectionError('HTTP transport server is already running');\n }\n\n await new Promise<void>((resolve, reject) => {\n const server = createServer((req, res) => {\n void (async () => {\n try {\n const requestPath = new URL(req.url ?? '/', `http://${req.headers.host ?? this.config.host}`).pathname;\n\n if (requestPath === '/health' && req.method === 'GET') {\n writeJson(res, 200, { status: 'ok', transport: 'streamable-http' });\n return;\n }\n\n if (requestPath !== this.config.path) {\n writeJson(res, 404, { error: 'Not found' });\n return;\n }\n\n await this.handleMcpRequest(req, res);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n writeJson(res, 500, {\n jsonrpc: '2.0',\n error: {\n code: -32603,\n message: cause.message,\n },\n id: null,\n });\n }\n })();\n });\n\n server.once('error', (error) => {\n reject(\n new TransportConnectionError('Failed to establish streamable HTTP transport connection', { cause: error }),\n );\n });\n\n server.listen(this.config.port, this.config.host, () => {\n this.httpServer = server;\n process.stderr.write(\n `browse-tool MCP server started on http://${this.config.host}:${this.config.port}${this.config.path}\\n`,\n );\n process.stderr.write(`Health check: http://${this.config.host}:${this.config.port}/health\\n`);\n resolve();\n });\n });\n }\n\n async stop(): Promise<void> {\n await this.sessionManager.clear();\n\n if (!this.httpServer) {\n return;\n }\n\n const server = this.httpServer;\n this.httpServer = null;\n\n await new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n });\n }\n}\n"],"mappings":"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"}