{"version":3,"file":"websocket-routes-xxrWcdQd.mjs","names":["err","execFileAsync","DEFAULT_ENVIRONMENT","err","fs","DOCKER_VM_ALIASES","chromeForTestingConfig.dockerImage","chromeForTestingConfig.dockerBrowserBinary","cloakBrowserConfig.dockerImage","cloakBrowserConfig.dockerBrowserBinary","DEBUG_EXTENSION_RECORDING_STDOUT","chromeForTestingConfig.dockerPlatform","cloakBrowserConfig.dockerPlatform","err","LOG_PREFIX","esbuildBuild","execFileAsync","ProxyConfigService","resolveWorkspaceRoot","browserOutputDirectory","getCurrentProfileName","assertPageOwnership","isOwnedByOtherAgent","browserOutputPath","getCurrentOwnerId","killProcessTree","shouldCleanupRecorderPageAfterStop","dockerPlatform","CHROME_FOR_TESTING_VERSION_ENV_VAR","dockerBrowserBinary","ChromeForTestingService","dockerImage","CHROME_FOR_TESTING_PATH_ENV_VAR","dockerBrowserBinary$1","ensureCloakBrowserImageAvailable","dockerImage$1","ensureManagedDockerChromeForTestingImageAvailable","dockerPlatform$1","MAX_TOOL_OUTPUT_CHARS","DEFAULT_TOOL_TIMEOUT_MS","DEFAULT_LOCATE_TIMEOUT_MS","DEFAULT_RUN_CODE_TIMEOUT_MS","DEFAULT_ACTION_TIMEOUT_MS","expect","initCollector","splitUseConfig","resetCollector","getCollectedSuite","SkipTestError","PAGE_PROXY_BRAND","LocatorProxy","TelemetryService","ExtensionToolDelegator","ExtensionTaskQueue","resolveServerIdleTimeoutMs","resolveBrowserIdleTimeoutMs","IdleCleanupService","SERVER_IDLE_TIMEOUT_MINUTES_ENV_VAR","BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR","fs","path","fs$1","os","playwright","LINK_TOKEN_HEADER","TOKEN_IN_TEXT","digest","createHash","value","redactLinkTokens","text","ExtensionLinkTokens","token","randomBytes","browserId","candidate","expected","timingSafeEqual","found","id","AutomationRunner","browserService","specRunner","specMetadataService","setupRunner","webServerManager","pageRegistry","providedId","existingBrowserId","existingPageId","options","automationId","session","specPath","isExistingPage","specResult","browserOptions","keepBrowserOpen","kept","newPageId","idx","pageId","error","serverResult","setupResult","config","configPath","configDir","dirname","startServer","runSetup","setupPath","setupExport","launchResult","specArgs","stopServerAfter","BrowserSessionConflictError","message","pid","userDataDir","BrowserLockManager","lockInfo","filesToClean","file","unlink","defaultLockPath","singletonLockPath","singletonSocketPath","match","symlinkPath","lockFilePath","content","readFile","execFileAsync$2","promisify","execFile","BROWSER_PROCESS_TAG","BrowserProcessRegistry","processRegistry","runtime","result","input","resolve","entries","entry","metadata","record","serverPid","ownedByCurrentServer","ownerServerAlive","commandLine","stdout","PROXY_AUTH_EXTENSION_PREFIX","createProxyAuthExtension","extDir","manifest","backgroundScript","username","password","removeProxyAuthExtension","extensionDir","buildProxyArgs","args","proxy","STEALTH_USER_DATA_PREFIX","StealthLauncher","profileService","lockManager","platform","paths","candidates","chromePath","currentDir","fileURLToPath","providedPath","profileName","profileDir","extensionPath","extensionPaths","proxyAuthExtensionDir","loadExtensions","chromeProcess","spawn","instance","err","closePromises","b","CLOAK_USER_DATA_PREFIX","HOUR_MS","SWEEP_RULES","cleanupOrphanedBrowserTempDirs","tempRoot","removed","rule","target","stats","now","DOCKER_VM_ALIASES$1","DEFAULT_VM_DOCKER_IMAGE","DEFAULT_VM_DOCKER_BROWSER_BINARY","CLOAKBROWSER_DOCKER_IMAGE","CLOAKBROWSER_DOCKER_BROWSER_BINARY","EXTENSION_RECORDING_ARTIFACT_WAIT_MS","DEBUG_EXTENSION_RECORDING_STDOUT$1","SESSION_RESTORE_ARTIFACT_NAMES","SESSION_RESTORE_PROFILE_DIRS","hasDisplay","BrowserLaunchBusyError","timeoutMs","BrowserService","chromeForTesting","pageMonitor","webSocketHub","extensionSessionRegistry","extensionTaskQueue","browserProcessRegistry","browser","requestedBrowserId","existingBrowser","onPageClose","releaseLease","profile","settings","isChromium","browserType","useCloak","headless","context","cloakTempDir","warning","executablePath","removeCloakTempDir","contextOptions","baseURL","recordVideo","storageState","page","browserInstance","onGone","release","browserTypeInstance","proxyConfig","requiresSandboxBypass","preferredChromiumExecutable","disableGpu","chromiumLaunchArgs","chromiumLaunchOptions","tempDir","firstRun","launchPersistentContext","state","cookies","remaining","launchMode","extensionHostPath","manifestPath","vmRuntime","dockerVolumeArgs","proxyAuthPath","proxyPathInContainer","autoSelectTabCaptureSourceTitle","autoSelectDesktopCaptureSource","useFakeUiForMediaStream","defaultArgs","isUrlLike","url","normalizeUrlLikeArg","parsed","normalizedPath","normalizedLaunchUrl","commandArgs","arg","dockerEnvWithDefaults","spawnArgs","lastError","dockerRetryCleanupAttempts","lastDockerRetryCleanupStatus","attempt","launchOperation","retryDiagnostics","command","extensionPathInContainer","requestedRuntimeExtensionPath","userDataDirInContainer","dockerBrowserType","vmDockerBrowser","envBrowser","explicit","docker","hostPath","containerPath","readOnly","stack","fullPath","dockerConfig","network","additionalVolumeArgs","hostUserDataDir","dockerEnv","additionalRunArgs","enableVnc","lines","browserBinary","key","rawArgs","trimmed","binaryName","packageRoot","localCandidatePaths","localExtPath","startDir","i","packageJsonPath","packageName","parentDir","artifactName","profileDirs","directoryEntries","currentPlatform","CHROME_PATHS","onClose","browsers","ownerPages","ownerId","pageIds","inFlight","handling","profileStateSaved","recordingError","recording","recordingOutputPath","hadActiveRecording","finalizeAfterClose","pages","p","streamedArtifactState","artifactArrived","ids","cutoff","resolvedOutputPath","outputPath","cleanupRecorderPage","stopResult","payload","fileBuffer","videoBase64","chunkBase64","chunkBuffer","a","oldest","browsersArray","oldestBrowser","chunks","totalBytes","push","chunk","remainingBytes","slice","source","process","processOutput","extNow","startupDelayMs","exitCode","exitSignal","processError","code","signal","stableSince","sawConnection","connectionFlaps","longestStableConnectionMs","firstConnectedAt","graceApplied","startedAt","startupTimeoutMs","startupGraceTimeoutMs","readyStabilityMs","startupPollIntervalMs","deadline","readyState","finalReadyState","output","logPath","details","vmRuntimeKind","releaseQueue","previousLaunch","operation","configured","predecessor","releaseMine","mine","myTail","timer","timeout","promise","ms","currentPageId","lockManagerCleaned","artifactPaths","artifactPath","errorMessage","finalizationPromise","timeoutPromise","artifactStats","chunkStats","artifactState","keepAliveResult","firstContent","outputStats","type","chromium","firefox","webkit","sanitizeSnippetName","name","indentCode","line","buildSnippetSource","loadSnippetModule","filePath","transform","CodeSnippetService","snippetsDir","directory","mkdir","readdir","snippets","snippetPath","loadedModule","description","left","right","resolvedPath","writeFile","stat","unquoteSelectorText","rawText","quote","parseSelectorShortcut","selector","hasTextMatch","ElementSelectorSchema","z","ElementLocatorService","spec","first","locator","frameSelector","exact","role","shortcut","parts","EXTENSION_PROXY_DEBUG_ENABLED","ELEMENT_HANDLE_MARKER_ATTR","summarizeArgs","debugProxy","resolveProxyValue","ExtensionPageProxy","taskQueue","values","mkdirSync","writeFileSync","script","fn","markerValue","functionSource","argsJson","markerAttrJson","markerValueJson","ExtensionElementHandleProxy","snapshot","href","responseTarget","urlOrPredicate","argsWithTarget","taskResult","tool","markerAttr","isElement","files","ExtensionSessionRegistry","existingSessionId","request","sessionId","randomUUID","mode","removedCount","maxAgeMs","CONTENT_TYPE_EXTENSIONS","sanitizePathSegment","attachmentExtension","nameExtension","extname","pathExtension","validateAttachment","createRunSpecTestInfo","attachments","outputDirectory","join","title","extension","rawBaseName","copyFile","property","receiver","ExtensionSpecRunner","bundler","pageProxy","sessionRegistry","bundleResult","proc","savedInitiator","startTime","testResults","passed","failed","handoffOccurred","warnings","serialFailures","suite","testsToRun","testFilter","incompleteReason","fixtures","onHandoff","test","unsupportedKeys","runtimeUse","serialScopeId","shouldSkipDueToSerial","testStartTime","testInfoAdapter","fixtureOverrides","filtered","tests","filter","onlyTests","t","pattern","PageMonitorService","response","location","requestId","msg","PageRegistry","tabId","notifyExtension","PROFILE_LEASE","ProfileService","homedir","root","realpath","chmod","existsSync","lstat","resolved","inside","relative","isAbsolute","sep","lock","openLease","open","handle","isStaleLease","identity","current","released","profiles","fullProfile","existing","updated","updates","rm","temporary","rename","SetupRunner","setupModule","exportName","module","defaultExport","availableExports","LOG_PREFIX$1","PLAYWRIGHT_TEST_IMPORT_FILTER","STUB_DEFINE_KEY","BUNDLE","ERROR_CODES","ERROR_MESSAGES","SpecBundlerBuildError","basename","buildErrors","SpecBundlerIoError","targetPath","SpecBundlerService","serviceDir","dir","outdir","tmpdir","cause","symlinkTarget","symlink","fileNameWithoutExt","stubPath","build","errors","hooksSourceDir","hooksPath","outputName","entryContents","SpecDiscoveryService","searchPath","basePath","projects","configPaths","projectPath","project","testDir","testsPath","specs","specPaths","specInfo","absolutePath","filteredSpecs","configs","isDir","dirName","dirPath","callback","testDirMatch","escaped","SpecMetadataService","specModule","argsSchema","baseURLMatch","mcpConfigMatch","mcpBlock","setupFileMatch","setupExportMatch","webServerMatch","webServerBlock","commandMatch","urlMatch","reuseMatch","timeoutMatch","cwdMatch","prefix","envPrefix","fieldName","schema","envKey","index","part","num","createContextOptions","useConfig","SpecRunner","keptPages","configWarnings","testContext","testPage","ctx","getResultError","ToolExecutor","delegator","telemetry","toolName","pageEntry","span","SpanStatusCode","playwrightExecutor","errorText","WebServerManager","cleanup","reuseExistingServer","workingDir","cwd","cmd","delay","resolvePromise","forceKillTimeout","controller","timeoutId","inQuote","quoteChar","char","ContentItemSchema","TaskTelemetryContextSchema","TaskResultContentSchema","TaskPushSchema","TaskCancelSchema","PageCreatedSchema","PageRemovedSchema","SessionAckSchema","PongSchema","ServerErrorSchema","SessionRegisterSchema","TaskResultSchema","TabMappedSchema","PingSchema","HeartbeatSchema","ExtensionMessageSchema","safeParseExtensionMessage","data","WebSocketHub","handlers","connectionId","connection","browserConns","connectionIds","conn","oldBrowserId","newBrowserId","oldBrowserConns","rawData","delivered","task","staleAfterMs","intervalMs","isStale","connections","messageId","c","BaseTool","toolExecutor","base64Data","ResolveLocatorToolInputSchema","resolveLocatorCandidates","resolveContext","selectorSpec","candidateLimit","normalizeText","matchesText","normalizedActual","actual","normalizedExpected","getLabelText","element","explicitLabel","wrappedLabel","getAccessibleName","ariaLabel","labelledBy","labels","getRole","ariaRole","tagName","SKIP_TAGS","scoreCandidate","accessibleName","label","placeholder","textContent","testId","rect","visible","enabled","score","semanticSignals","reasons","isInteractive","allCandidates","ResolveLocatorTool","frame","ClickToolInputSchema","ClickTool","elementLocator","CloseBrowserToolInputSchema","CloseBrowserTool","pageCount","ClosePageToolInputSchema","ClosePageTool","wasCurrentPage","isLastPage","outcome","newCurrentPageId","CreateProfileToolInputSchema","CreateProfileTool","DeleteProfileToolInputSchema","DeleteProfileTool","DiscoverSpecsToolInputSchema","DiscoverSpecsTool","specDiscoveryService","includeMetadata","sum","TargetSchema","DragToolInputSchema","DragTool","sourceLocator","targetLocator","EmulateToolInputSchema","EmulateTool","appliedSettings","EvaluateScriptToolInputSchema","EvaluateScriptTool","serializedResult","filename","ExpectToolInputSchema","ExpectTool","extensionPageProxy","negate","pageLevelAssertions","assertion","expectation","attributeName","FillToolInputSchema","FillTool","GetNetworkRequestToolInputSchema","GetNetworkRequestTool","GoBackToolInputSchema","GoBackTool","status","GoForwardToolInputSchema","GoForwardTool","HandleDialogToolInputSchema","HandleDialogTool","onDialog","dialog","HoverToolInputSchema","HoverTool","InspectElementToolInputSchema","InspectElementTool","matchCount","firstMatch","node","inspection","execFileAsync$1","ASN_PATTERN","lsappinfo","frontAsn","activateAsn","asn","withPreservedFocus","launch","before","running","restoreIfStolen","polling","ABOUT_BLANK_URL","PO_EXTENSION_LINK_PATH","DEBUG_EXTENSION_RECORDING_STDOUT","DEFAULT_DOCKER_VM_SERVER_HOST","LOG_PREFIX","DOCKER_VM_ALIASES","execFileAsync","resolveGitCommonRoot","startPath","gitCommonDir","repoRoot","proxyConfigSchema","LaunchBrowserToolInputSchema","LaunchBrowserTool","stealthLauncher","proxyConfigService","linkTokens","explicitMode","requestedInput","boundProfile","launchInput","reason","fallbackWarning","navigateUrl","serverUrl","bootstrapUrl","vmCommand","dockerRuntimeOptions","vmDetails","\"t\"","inputVmServerUrl","serverOrigin","host","registryHostPort","buildPlaywrightBaseUrl","rawUrl","shouldExposeVnc","runArgs","env","vnc","hostVncPort","hostNoVncPort","origin","port","registry","fallback","DEFAULT_MCP_PORT","repositoryPath","environment","portRegistry","PortRegistryService","baseQuery","tryGetPort","repoPath","scoped","gitCommonRoot","fromRoot","r","ListConsoleMessagesToolInputSchema","ListConsoleMessagesTool","messages","m","ListNetworkRequestsToolInputSchema","ListNetworkRequestsTool","requests","ListPagesToolInputSchema","ListPagesTool","pageList","ListProfilesToolInputSchema","ListProfilesTool","ListSnippetsToolInputSchema","ListSnippetsTool","snippetService","NavigateToolInputSchema","NavigateTool","NewPageToolInputSchema","NewPageTool","setAsCurrent","delegatedText","delegatedPayload","navError","PdfToolInputSchema","PdfTool","PressKeyToolInputSchema","PressKeyTool","ReloadToolInputSchema","ReloadTool","ResizePageToolInputSchema","ResizePageTool","viewportSize","RunCodeToolInputSchema","RunCodeTool","maybeRunner","runtimePage","runtimeContext","runtimeBrowser","savedSnippet","reject","run","snippet","DEFAULT_VIDEO_SIZE","RunSpecToolInputSchema","RunSpecTool","automationRunner","extensionSpecRunner","specBundlerService","hooksDurationMs","resolvedHooksPath","hooksStartedAt","hooksResult","executionStartedAt","incomplete","specFailed","category","depth","sourceDir","hooksModule","explicitHooksPath","preflight","shouldRecord","videoPath","requestedPageId","heartbeatAgeMs","requestedPage","recoveryCandidates","isError","SaveProfileStateToolInputSchema","SaveProfileStateTool","ScreenshotToolInputSchema","ScreenshotTool","imageType","SelectPageToolInputSchema","SelectPageTool","SelectToolInputSchema","SelectTool","selected","createSmartDiff","patch","structuredPatch","oldContent","newContent","addedLines","removedLines","hunk","maxLines","changeRatio","diffLines","diffString","threshold","searchSnapshot","needle","search","matchIndices","included","end","sorted","lineIdx","SnapshotToolInputSchema","SnapshotTool","raw","body","sizeThreshold","previous","fresh","diff","selectors","requestedRoot","StartRecordingToolInputSchema","StartRecordingTool","StartTraceToolInputSchema","StartTraceTool","StopRecordingToolInputSchema","StopRecordingTool","StopTraceToolInputSchema","StopTraceTool","tracePath","TypeToolInputSchema","TypeTool","UploadFileToolInputSchema","UploadFileTool","fileCount","WaitForToolInputSchema","WaitForTool","waitType","duration","createBrowseRuntime","workspaceRoot","ProcessRegistryService","idleCleanup","specBundler","specMetadata","toolList","tools","disposed","results","readBrowseSettings","globalPoConfigPath","browse","parsePoConfig","fromEnv","positiveInt","browseOptionsFromEnv","\"BROWSE_TOOL_PROXY_CONFIG_DIR\"","SessionViewScreencast","stream","latest","screenshot","stored","at","OWNER_PATTERN","CONTENT_TYPES","isSessionViewOwner","owner","toViewUrl","createSessionView","screencast","refreshMetadata","cap","capture","format","pending","next","owned","view","crash","live","since","errorName","setupWebSocketRoutes","app","upgradeWebSocket","ws","deps","wsHub","event","wireExtensionHub","existingPages"],"sources":["../src/server/extensionLinkTokens.ts","../src/services/AutomationRunner.ts","../src/services/BrowserLockManager.ts","../src/services/BrowserProcessRegistry.ts","../src/utils/proxyAuthExtension.ts","../src/services/StealthLauncher.ts","../src/utils/tempDirCleanup.ts","../src/services/BrowserService.ts","../src/services/CodeSnippetService.ts","../src/services/ElementLocatorService.ts","../src/services/ExtensionPageProxy.ts","../src/services/ExtensionSessionRegistry.ts","../src/services/TestInfoAdapter.ts","../src/services/ExtensionSpecRunner.ts","../src/services/PageMonitorService.ts","../src/services/PageRegistry.ts","../src/services/ProfileService.ts","../src/services/SetupRunner.ts","../src/services/SpecBundlerService.ts","../src/services/SpecDiscoveryService.ts","../src/services/SpecMetadataService.ts","../src/services/SpecRunner.ts","../src/services/ToolExecutor.ts","../src/services/WebServerManager.ts","../src/protocol/extension-messages.ts","../src/services/WebSocketHub.ts","../src/tools/BaseTool.ts","../src/tools/ResolveLocatorTool.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/InspectElementTool.ts","../src/utils/preserveFocus.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/utils/snapshotText.ts","../src/tools/SnapshotTool.ts","../src/tools/StartRecordingTool.ts","../src/tools/StartTraceTool.ts","../src/tools/StopRecordingTool.ts","../src/tools/StopTraceTool.ts","../src/tools/TypeTool.ts","../src/tools/UploadFileTool.ts","../src/tools/WaitForTool.ts","../src/container/index.ts","../src/services/SessionViewScreencast.ts","../src/services/SessionView.ts","../src/server/websocket-routes.ts"],"sourcesContent":["/**\n * Per-launch tokens for the Chrome extension link.\n *\n * Each extension-mode launch gets 32 random bytes bound to its browserId. They live\n * in memory only, so a server restart invalidates every link. Comparison runs over\n * sha256 digests with `timingSafeEqual`, so neither length nor content leaks timing.\n */\n\nimport { createHash, randomBytes, timingSafeEqual } from 'node:crypto';\n\n/** URL fragment key carrying the token on the bootstrap URL. */\nexport const LINK_TOKEN_FRAGMENT_KEY = 't';\n/** Query parameter carrying the token on the WebSocket upgrade. */\nexport const LINK_TOKEN_QUERY_PARAM = 't';\n/** Header carrying the token on extension HTTP requests. */\nexport const LINK_TOKEN_HEADER = 'x-browse-token';\n\nconst TOKEN_BYTES = 32;\nconst REDACTED = '[redacted]';\nconst TOKEN_IN_TEXT = /([#?&]t=|x-browse-token[\"']?\\s*[:=]\\s*[\"']?)[A-Za-z0-9_-]+/gi;\n\nfunction digest(value: string): Buffer {\n  return createHash('sha256').update(value).digest();\n}\n\n/** Replace every link token in a string, for anything that may be logged or returned. */\nexport function redactLinkTokens(text: string): string {\n  return text.replace(TOKEN_IN_TEXT, `$1${REDACTED}`);\n}\n\nexport class ExtensionLinkTokens {\n  private readonly tokens = new Map<string, { token: string; digest: Buffer }>();\n\n  /** Issue a fresh token for a browser, replacing any earlier one. */\n  issue(browserId: string): string {\n    const token = randomBytes(TOKEN_BYTES).toString('base64url');\n    this.tokens.set(browserId, { token, digest: digest(token) });\n    return token;\n  }\n\n  /** The browser's current token, for a launch that reuses a running browser. */\n  current(browserId: string): string | undefined {\n    return this.tokens.get(browserId)?.token;\n  }\n\n  revoke(browserId: string): void {\n    this.tokens.delete(browserId);\n  }\n\n  revokeAll(): void {\n    this.tokens.clear();\n  }\n\n  /**\n   * The browserId a token belongs to, or undefined. With a browserId, only that\n   * browser's token matches. Every candidate is compared, so a miss costs the same\n   * as a hit.\n   */\n  match(token: string | null | undefined, browserId?: string): string | undefined {\n    if (!token) return undefined;\n    const candidate = digest(token);\n    if (browserId !== undefined) {\n      const expected = this.tokens.get(browserId);\n      return expected && timingSafeEqual(expected.digest, candidate) ? browserId : undefined;\n    }\n    let found: string | undefined;\n    for (const [id, expected] of this.tokens) {\n      if (timingSafeEqual(expected.digest, candidate)) found = id;\n    }\n    return found;\n  }\n\n  verify(browserId: string | undefined, token: string | null | undefined): boolean {\n    return this.match(token, browserId) !== undefined;\n  }\n}\n","/**\n * AutomationRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for spec file execution\n * - Constructor injection; createBrowseRuntime wires the instances\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 { dirname } from 'node:path';\n\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/**\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\nexport class AutomationRunner implements IAutomationRunner {\n  private sessions: Map<string, AutomationRunSession> = new Map();\n  private sessionIdCounter = 0;\n\n  constructor(\n    private browserService: IBrowserService,\n    private specRunner: ISpecRunner,\n    private specMetadataService: ISpecMetadataService,\n    private setupRunner: ISetupRunner,\n    private webServerManager: IWebServerManager,\n    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      browserId: existingBrowserId,\n      pageId: existingPageId,\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    const isExistingPage = !!(existingBrowserId && existingPageId);\n\n    try {\n      if (isExistingPage) {\n        session.browserId = existingBrowserId!;\n        session.pageIds.push(existingPageId!);\n      }\n\n      session.status = 'running';\n      session.updatedAt = new Date();\n\n      const specResult = await this.specRunner.executeSpec({\n        specPath,\n        sessionId: automationId,\n        browserType: browserOptions.browserType,\n        headless: browserOptions.headless,\n        hooksPath,\n        baseURL: browserOptions.baseURL,\n        keepBrowserOpen,\n      });\n\n      if (keepBrowserOpen && specResult.browser) {\n        const browserId = this.browserService.registerExternalBrowser(specResult.browser);\n        session.browserId = browserId;\n\n        if (specResult.keptPages?.length) {\n          for (const kept of specResult.keptPages) {\n            const newPageId = await this.pageRegistry.register({\n              page: kept.page,\n              context: kept.context,\n              browserId,\n              onClose: (pageId) => {\n                const idx = session.pageIds.indexOf(pageId);\n                if (idx >= 0) session.pageIds.splice(idx, 1);\n              },\n            });\n            session.pageIds.push(newPageId);\n          }\n        }\n      }\n\n      session.status = 'completed';\n      session.updatedAt = new Date();\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      session.error = error instanceof Error ? error.message : String(error);\n      session.updatedAt = new Date();\n\n      return {\n        automationId,\n        status: 'failed',\n        pageIds: isExistingPage ? [...session.pageIds] : [],\n        browserId: 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      testFilter,\n      specArgs,\n      configPath,\n      runSetup = false,\n      startServer = false,\n      stopServerAfter = true,\n      browserId: existingBrowserId,\n      pageId: existingPageId,\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      const isExistingPage = !!(existingBrowserId && existingPageId);\n\n      if (isExistingPage) {\n        session.browserId = existingBrowserId!;\n        session.pageIds.push(existingPageId!);\n      }\n\n      session.status = 'running';\n      session.updatedAt = new Date();\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        const launchResult = await this.browserService.launch(browserOptions);\n        try {\n          setupResult = await this.setupRunner.runSetup(setupPath, setupExport, {\n            browser: launchResult.browserInstance.browser,\n            context: launchResult.browserInstance.context!,\n            page: launchResult.page,\n          });\n\n          if (!setupResult.success) {\n            throw new Error(setupResult.error ?? 'Setup failed');\n          }\n        } finally {\n          await this.browserService.closeBrowser(launchResult.browserInstance.id);\n        }\n      }\n\n      const specResult = await this.specRunner.executeSpecEnhanced({\n        specPath,\n        sessionId: automationId,\n        browserType: browserOptions.browserType,\n        headless: browserOptions.headless,\n        testFilter,\n        specArgs: { ...specArgs, ...setupResult?.state },\n        hooksPath,\n        baseURL: browserOptions.baseURL,\n        keepBrowserOpen,\n      });\n\n      if (keepBrowserOpen && specResult.browser) {\n        const browserId = this.browserService.registerExternalBrowser(specResult.browser);\n        session.browserId = browserId;\n\n        if (specResult.keptPages?.length) {\n          for (const kept of specResult.keptPages) {\n            const newPageId = await this.pageRegistry.register({\n              page: kept.page,\n              context: kept.context,\n              browserId,\n              onClose: (pageId) => {\n                const idx = session.pageIds.indexOf(pageId);\n                if (idx >= 0) session.pageIds.splice(idx, 1);\n              },\n            });\n            session.pageIds.push(newPageId);\n          }\n        }\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      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\n      return {\n        automationId,\n        status: 'failed',\n        pageIds: isExistingPage ? [...session.pageIds] : [],\n        browserId: 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 * 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 fs from 'node:fs';\nimport { readFile, unlink } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\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\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","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport { type ProcessRegistryRecord, type ProcessRegistryService } from '@agimon-ai/foundation-process-registry';\n\nimport { redactLinkTokens } from '../server/extensionLinkTokens.js';\nimport { getCurrentOwnerId } from '../utils/ownerContext.js';\nimport { killProcessTree } from '../utils/processTree.js';\nimport { resolveWorkspaceRoot } from '../utils/workspaceRoot.js';\nimport type { BrowserMode } from './BrowserService.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst BROWSER_PROCESS_TAG = 'browse-tool-browser';\nconst BROWSER_PROCESS_SERVICE_PREFIX = 'browse-tool-browser-';\nconst DEFAULT_ENVIRONMENT = 'development';\n\nexport interface BrowserProcessRegistration {\n  browserId: string;\n  profileName?: string;\n  userDataDir: string;\n  pid: number;\n  command: string;\n  args: string[];\n  serverPid?: number;\n  serverPort?: number;\n  ownerId?: string;\n  mode: BrowserMode;\n}\n\nexport interface BrowserProcessMetadata {\n  browserId: string;\n  profileName?: string;\n  userDataDir: string;\n  serverPid?: number;\n  serverPort?: number;\n  ownerId?: string;\n  mode?: BrowserMode;\n}\n\nexport interface BrowserProcessRuntime {\n  repositoryPath?: string;\n  environment?: string;\n  isProcessRunning?: (pid: number) => boolean;\n  getProcessCommandLine?: (pid: number) => Promise<string>;\n  killProcessTree?: (pid: number) => Promise<void>;\n  cleanupProfileLocks?: (userDataDir: string) => Promise<void>;\n}\n\nexport interface BrowserProcessReconcileOptions {\n  currentServerPid?: number;\n  currentPort?: number;\n  reconnectGraceMs?: number;\n}\n\nexport class BrowserProcessRegistry {\n  private readonly repositoryPath: string;\n  private readonly environment: string;\n  private readonly isProcessRunningFn: (pid: number) => boolean;\n  private readonly getProcessCommandLineFn: (pid: number) => Promise<string>;\n  private readonly killProcessTreeFn: (pid: number) => Promise<void>;\n  private readonly cleanupProfileLocksFn?: (userDataDir: string) => Promise<void>;\n\n  constructor(\n    private readonly processRegistry: ProcessRegistryService,\n    runtime: BrowserProcessRuntime = {},\n  ) {\n    this.repositoryPath = runtime.repositoryPath ?? resolveWorkspaceRoot({ env: {} });\n    this.environment = runtime.environment ?? DEFAULT_ENVIRONMENT;\n    this.isProcessRunningFn = runtime.isProcessRunning ?? ((pid) => this.isProcessRunning(pid));\n    this.getProcessCommandLineFn = runtime.getProcessCommandLine ?? ((pid) => this.getProcessCommandLine(pid));\n    this.killProcessTreeFn = runtime.killProcessTree ?? killProcessTree;\n    this.cleanupProfileLocksFn = runtime.cleanupProfileLocks;\n  }\n\n  async registerBrowserProcess(input: BrowserProcessRegistration): Promise<void> {\n    const result = await this.processRegistry.registerProcess({\n      repositoryPath: this.repositoryPath,\n      serviceName: this.serviceName(input.browserId),\n      serviceType: 'tool',\n      environment: this.environment,\n      pid: input.pid,\n      command: input.command,\n      // The bootstrap URL carries the extension link token; the registry file must not.\n      args: input.args.map(redactLinkTokens),\n      tags: [BROWSER_PROCESS_TAG],\n      metadata: {\n        browserId: input.browserId,\n        profileName: input.profileName,\n        userDataDir: input.userDataDir,\n        serverPid: input.serverPid ?? process.pid,\n        serverPort: input.serverPort,\n        ownerId: input.ownerId ?? getCurrentOwnerId(),\n        mode: input.mode,\n      },\n      force: true,\n    });\n\n    if (!result.success) {\n      throw new Error(result.error ?? `Failed to register browser process \"${input.browserId}\"`);\n    }\n  }\n\n  async releaseBrowserProcess(browserId: string, options: { pid?: number } = {}): Promise<void> {\n    const result = await this.processRegistry.releaseProcess({\n      repositoryPath: this.repositoryPath,\n      serviceName: this.serviceName(browserId),\n      serviceType: 'tool',\n      environment: this.environment,\n      pid: options.pid,\n      kill: false,\n      releasePort: false,\n    });\n\n    if (!result.success && result.error && !result.error.includes('No matching process entry')) {\n      throw new Error(result.error);\n    }\n  }\n\n  async findBrowserProcess(browserId: string): Promise<ProcessRegistryRecord | undefined> {\n    const entries = await this.processRegistry.listProcesses({\n      repositoryPath: this.repositoryPath,\n      serviceType: 'tool',\n      serviceName: this.serviceName(browserId),\n      environment: this.environment,\n      tags: [BROWSER_PROCESS_TAG],\n    });\n    return entries[0];\n  }\n\n  async reconcileOrphanedBrowsers(options: BrowserProcessReconcileOptions = {}): Promise<void> {\n    if (options.reconnectGraceMs && options.reconnectGraceMs > 0) {\n      await new Promise((resolve) => setTimeout(resolve, options.reconnectGraceMs));\n    }\n\n    const entries = await this.processRegistry.listProcesses({\n      repositoryPath: this.repositoryPath,\n      serviceType: 'tool',\n      environment: this.environment,\n      tags: [BROWSER_PROCESS_TAG],\n    });\n\n    for (const entry of entries) {\n      await this.reconcileEntry(entry, options);\n    }\n  }\n\n  parseMetadata(record: ProcessRegistryRecord): BrowserProcessMetadata | undefined {\n    const metadata = record.metadata ?? {};\n    const browserId = typeof metadata.browserId === 'string' ? metadata.browserId : undefined;\n    const userDataDir = typeof metadata.userDataDir === 'string' ? metadata.userDataDir : undefined;\n    if (!browserId || !userDataDir) {\n      return undefined;\n    }\n\n    return {\n      browserId,\n      userDataDir,\n      profileName: typeof metadata.profileName === 'string' ? metadata.profileName : undefined,\n      serverPid: typeof metadata.serverPid === 'number' ? metadata.serverPid : undefined,\n      serverPort: typeof metadata.serverPort === 'number' ? metadata.serverPort : undefined,\n      ownerId: typeof metadata.ownerId === 'string' ? metadata.ownerId : undefined,\n      mode: this.parseBrowserMode(metadata.mode),\n    };\n  }\n\n  private async reconcileEntry(entry: ProcessRegistryRecord, options: BrowserProcessReconcileOptions): Promise<void> {\n    const metadata = this.parseMetadata(entry);\n    if (!metadata) {\n      await this.releaseEntry(entry);\n      return;\n    }\n\n    if (!this.isProcessRunningFn(entry.pid)) {\n      await this.releaseEntry(entry);\n      await this.cleanupProfileLocksFn?.(metadata.userDataDir);\n      return;\n    }\n\n    const serverPid = metadata.serverPid;\n    const ownedByCurrentServer = serverPid === (options.currentServerPid ?? process.pid);\n    const ownerServerAlive = typeof serverPid === 'number' && this.isProcessRunningFn(serverPid);\n    if (ownedByCurrentServer || ownerServerAlive) {\n      return;\n    }\n\n    const commandLine = await this.getProcessCommandLineFn(entry.pid);\n    if (!this.isVerifiedBrowserProcess(entry, metadata, commandLine)) {\n      await this.releaseEntry(entry);\n      return;\n    }\n\n    await this.killProcessTreeFn(entry.pid);\n    await this.cleanupProfileLocksFn?.(metadata.userDataDir);\n    await this.releaseEntry(entry);\n  }\n\n  private async releaseEntry(entry: ProcessRegistryRecord): Promise<void> {\n    const metadata = this.parseMetadata(entry);\n    const browserId = metadata?.browserId ?? entry.serviceName.slice(BROWSER_PROCESS_SERVICE_PREFIX.length);\n    await this.releaseBrowserProcess(browserId, { pid: entry.pid });\n  }\n\n  private isVerifiedBrowserProcess(\n    entry: ProcessRegistryRecord,\n    metadata: BrowserProcessMetadata,\n    commandLine: string,\n  ): boolean {\n    void entry;\n    return commandLine.includes(metadata.userDataDir) || commandLine.includes(metadata.browserId);\n  }\n\n  private serviceName(browserId: string): string {\n    return `${BROWSER_PROCESS_SERVICE_PREFIX}${browserId.replace(/[^a-zA-Z0-9_.-]/g, '-')}`;\n  }\n\n  private parseBrowserMode(value: unknown): BrowserMode | undefined {\n    if (value === 'playwright' || value === 'extension' || value === 'vm') {\n      return value;\n    }\n    return undefined;\n  }\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 async getProcessCommandLine(pid: number): Promise<string> {\n    try {\n      const { stdout } = await execFileAsync('ps', ['-p', String(pid), '-o', 'command=']);\n      return stdout.trim();\n    } catch {\n      return '';\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/** Prefix of every generated proxy-auth extension directory. */\nexport const PROXY_AUTH_EXTENSION_PREFIX = 'proxy-auth-ext-';\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  // The generated script embeds the proxy credentials in plaintext, so the\n  // directory and its files are readable only by this user.\n  const extDir = fs.mkdtempSync(path.join(os.tmpdir(), PROXY_AUTH_EXTENSION_PREFIX));\n  fs.chmodSync(extDir, 0o700);\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), { mode: 0o600 });\n  fs.writeFileSync(path.join(extDir, 'background.js'), backgroundScript, { mode: 0o600 });\n\n  return extDir;\n}\n\n/**\n * Remove a directory created by {@link createProxyAuthExtension}.\n *\n * Chrome reads the extension for the browser's whole lifetime, so this must run\n * at browser close rather than right after launch. Only paths matching the\n * generated prefix are touched.\n */\nexport function removeProxyAuthExtension(extensionDir: string | undefined): void {\n  if (!extensionDir || !path.basename(extensionDir).startsWith(PROXY_AUTH_EXTENSION_PREFIX)) {\n    return;\n  }\n\n  try {\n    fs.rmSync(extensionDir, { recursive: true, force: true });\n  } catch {\n    // Best effort: a leftover directory is swept on the next daemon start.\n  }\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 * 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 { 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';\n\nimport type { BrowseOptions } from '../types/index.js';\nimport { killProcessTree } from '../utils/processTree.js';\nimport {\n  buildProxyArgs,\n  createProxyAuthExtension,\n  type ProxyConfig,\n  removeProxyAuthExtension,\n} from '../utils/proxyAuthExtension.js';\nimport type { IBrowserLockManager } from './BrowserLockManager.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport type { IProfileService } from './ProfileService.js';\n\n/** Prefix of every generated stealth user-data directory. */\nexport const STEALTH_USER_DATA_PREFIX = 'stealth-chrome-';\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  /** Registry id of the page this browser was registered with */\n  pageId: string;\n  /** User data directory path */\n  userDataDir: string;\n  /** Extension path used */\n  extensionPath: string;\n  /** Temp directory holding generated proxy credentials, removed on close */\n  proxyAuthExtensionDir?: string;\n  /** Profile name if specified */\n  profileName?: string;\n  /** Creation timestamp */\n  createdAt: Date;\n  /** Timestamp of the last agent activity against this browser, used for idle cleanup */\n  lastAccessedAt: 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  /** Refreshes lastAccessedAt. Returns false when the browser is unknown. */\n  touch(browserId: string): boolean;\n  findChromePath(): string | null;\n}\n\nexport class StealthLauncher implements IStealthLauncher {\n  private browsers: Map<string, StealthBrowserInstance> = new Map();\n  private browserIdCounter = 0;\n\n  constructor(\n    private profileService: IProfileService,\n    private pageRegistry: IPageRegistry,\n    private lockManager: IBrowserLockManager,\n    private readonly options: Pick<BrowseOptions, 'extensionPath'> = {},\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    // Explicit or configured path first, then dist/extension of this package,\n    // resolved from this module so it never depends on the caller's directory.\n    const currentDir = path.dirname(fileURLToPath(import.meta.url));\n    const candidates = [\n      providedPath,\n      this.options.extensionPath,\n      path.resolve(currentDir, '../dist/extension'),\n      path.resolve(currentDir, '../../dist/extension'),\n    ].filter((candidate): candidate is string => Boolean(candidate));\n\n    for (const candidate of candidates) {\n      if (fs.existsSync(path.join(candidate, 'manifest.json'))) {\n        return candidate;\n      }\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    // mkdtemp rather than a timestamp: two launches in the same millisecond\n    // would otherwise share a directory and the second Chrome would refuse to\n    // start on the profile's singleton lock.\n    return fs.mkdtempSync(path.join(os.tmpdir(), STEALTH_USER_DATA_PREFIX));\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    let proxyAuthExtensionDir: string | undefined;\n    if (options.proxy?.username && options.proxy?.password) {\n      proxyAuthExtensionDir = createProxyAuthExtension(options.proxy.username, options.proxy.password);\n      extensionPaths.push(proxyAuthExtensionDir);\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      '--silent-debugger-extension-api',\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    // Register a virtual page entry for this browser (extension mode - no Playwright objects)\n    const pageId = this.pageRegistry.registerExtensionPage(browserId);\n\n    const instance: StealthBrowserInstance = {\n      id: browserId,\n      process: chromeProcess,\n      pid: chromeProcess.pid,\n      pageId,\n      userDataDir,\n      extensionPath,\n      proxyAuthExtensionDir,\n      profileName: options.profileName,\n      createdAt: new Date(),\n      lastAccessedAt: new Date(),\n      isRunning: true,\n    };\n\n    // Track process lifecycle. A Chrome that dies on its own must leave no\n    // registry entry or temp directory behind, otherwise nothing ever reaps it:\n    // the idle sweep only looks at instances still marked running.\n    chromeProcess.on('exit', () => {\n      this.handleProcessExit(browserId);\n    });\n\n    chromeProcess.on('error', (err) => {\n      console.error(`Chrome process ${browserId} error:`, err);\n      this.handleProcessExit(browserId);\n    });\n\n    this.browsers.set(browserId, instance);\n\n    return instance;\n  }\n\n  /** Forget an instance whose Chrome process is gone, releasing its artifacts. */\n  private handleProcessExit(browserId: string): void {\n    const instance = this.browsers.get(browserId);\n    if (!instance) {\n      return;\n    }\n\n    instance.isRunning = false;\n    this.browsers.delete(browserId);\n    this.cleanupInstanceArtifacts(instance);\n  }\n\n  /** Remove the registry entry and temp directories belonging to an instance. */\n  private cleanupInstanceArtifacts(instance: StealthBrowserInstance): void {\n    this.pageRegistry.remove(instance.pageId);\n    removeProxyAuthExtension(instance.proxyAuthExtensionDir);\n\n    // Named profiles live outside tmp and must survive; only generated\n    // directories are removed.\n    const isGeneratedUserDataDir =\n      !instance.profileName && path.basename(instance.userDataDir).startsWith(STEALTH_USER_DATA_PREFIX);\n    if (!isGeneratedUserDataDir) {\n      return;\n    }\n\n    try {\n      fs.rmSync(instance.userDataDir, { recursive: true, force: true });\n    } catch {\n      // Best effort: leftovers are swept by age on the next daemon start.\n    }\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    this.cleanupInstanceArtifacts(instance);\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   * Refreshes the idle timestamp for a stealth browser.\n   */\n  touch(browserId: string): boolean {\n    const instance = this.browsers.get(browserId);\n    if (!instance) {\n      return false;\n    }\n\n    instance.lastAccessedAt = new Date();\n    return true;\n  }\n}\n","/**\n * Startup sweep for browser temp directories.\n *\n * Browser launches create temp directories that are removed when the browser\n * closes, but a daemon that is SIGKILLed or crashes never gets to do that. The\n * daemon is shared and long-lived, so those leftovers accumulate — including\n * proxy-auth extensions holding plaintext credentials.\n *\n * Another daemon may be running concurrently, so age is the only safe way to\n * tell an abandoned directory from one currently in use.\n */\n\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { STEALTH_USER_DATA_PREFIX } from '../services/StealthLauncher.js';\nimport { PROXY_AUTH_EXTENSION_PREFIX } from './proxyAuthExtension.js';\n\n/** Prefix used for generated extension-mode user-data directories. */\nexport const EXTENSION_USER_DATA_PREFIX = 'extension-chrome-';\n\n/** Prefix used for profile-less cloak engine user-data directories. */\nexport const CLOAK_USER_DATA_PREFIX = 'cloak-chrome-';\n\nconst HOUR_MS = 60 * 60 * 1000;\n\n/** Directories holding credentials expire sooner than plain browser profiles. */\nconst SWEEP_RULES: ReadonlyArray<{ prefix: string; maxAgeMs: number }> = [\n  { prefix: PROXY_AUTH_EXTENSION_PREFIX, maxAgeMs: 6 * HOUR_MS },\n  { prefix: STEALTH_USER_DATA_PREFIX, maxAgeMs: 24 * HOUR_MS },\n  { prefix: EXTENSION_USER_DATA_PREFIX, maxAgeMs: 24 * HOUR_MS },\n  { prefix: CLOAK_USER_DATA_PREFIX, maxAgeMs: 24 * HOUR_MS },\n];\n\n/**\n * Remove abandoned browser temp directories.\n *\n * Never throws: a sweep failure must not stop the daemon from starting.\n *\n * @returns Number of directories removed\n */\nexport async function cleanupOrphanedBrowserTempDirs(now = Date.now()): Promise<number> {\n  const tempRoot = os.tmpdir();\n  let removed = 0;\n\n  let entries: string[];\n  try {\n    entries = await fs.readdir(tempRoot);\n  } catch {\n    return 0;\n  }\n\n  for (const entry of entries) {\n    const rule = SWEEP_RULES.find((candidate) => entry.startsWith(candidate.prefix));\n    if (!rule) {\n      continue;\n    }\n\n    const target = path.join(tempRoot, entry);\n    try {\n      const stats = await fs.stat(target);\n      if (!stats.isDirectory() || now - stats.mtimeMs < rule.maxAgeMs) {\n        continue;\n      }\n\n      await fs.rm(target, { recursive: true, force: true });\n      removed += 1;\n    } catch {\n      // Skip anything we cannot stat or remove; the next sweep retries.\n    }\n  }\n\n  if (removed > 0) {\n    console.log(`[TempCleanup] Removed ${removed} orphaned browser temp director${removed === 1 ? 'y' : 'ies'}`);\n  }\n\n  return removed;\n}\n","/**\n * BrowserService\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Constructor injection; createBrowseRuntime wires the instances\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 { 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';\n\nimport type { Browser, BrowserContext, BrowserType, Page } from 'playwright';\nimport { chromium, firefox, webkit } from 'playwright';\n\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport cloakBrowserConfig from '../config/cloakbrowser.json';\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { BrowseOptions, BrowseVmDockerOptions } from '../types/index.js';\nimport {\n  ensureCloakBrowserImageAvailable,\n  ensureManagedDockerChromeForTestingImageAvailable,\n} from '../utils/dockerChromeForTesting.js';\nimport { browserOutputDirectory, getCurrentOwnerId } from '../utils/ownerContext.js';\nimport { killProcessTree } from '../utils/processTree.js';\nimport { buildProxyArgs, createProxyAuthExtension, type ProxyConfig } from '../utils/proxyAuthExtension.js';\nimport { CLOAK_USER_DATA_PREFIX } from '../utils/tempDirCleanup.js';\nimport { BrowserSessionConflictError, type IBrowserLockManager } from './BrowserLockManager.js';\nimport type { BrowserProcessRegistry } from './BrowserProcessRegistry.js';\nimport type { IChromeForTestingService } from './ChromeForTestingService.js';\nimport { shouldCleanupRecorderPageAfterStop } from './ChromeForTestingService.js';\nimport type { IExtensionSessionRegistry } from './ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue, ExtensionTaskResult } 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([\n  'docker',\n  'docker-vm',\n  'docker-chromium',\n  'docker-chrome-testing',\n  'docker-cloakbrowser',\n]);\nconst DEFAULT_VM_DOCKER_IMAGE = chromeForTestingConfig.dockerImage;\nconst DEFAULT_VM_DOCKER_BROWSER_BINARY = chromeForTestingConfig.dockerBrowserBinary;\nconst CLOAKBROWSER_DOCKER_IMAGE = cloakBrowserConfig.dockerImage;\nconst CLOAKBROWSER_DOCKER_BROWSER_BINARY = cloakBrowserConfig.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-browse';\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/** Headed windows need a display; only Linux can lack one (macOS and Windows always have it). */\nexport function hasDisplay(): boolean {\n  return process.platform !== 'linux' || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);\n}\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  /** Extension user data directory for managed Chrome processes */\n  userDataDir?: string;\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  /** Set for CloakBrowser persistent contexts; they have no Browser object and keep state in user-data. */\n  engine?: 'cloak';\n  /** Resolved headless flag for Playwright launches. */\n  headless?: boolean;\n  /** Owners that registered a page in this browser, read when it goes away unexpectedly. */\n  ownerIds?: Set<string>;\n}\n\n/** A browser that went away without browse-tool closing it. */\nexport interface BrowserCrashRecord {\n  browserId: string;\n  profileName?: string;\n  headless?: boolean;\n  ownerIds: string[];\n  /** Epoch ms when the browser went away. */\n  at: number;\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  /** Set when the launch did not get the engine it asked for, so the caller can say so. */\n  warning?: string;\n}\n\nexport type VmDockerBrowserType = 'cloakbrowser' | 'chrome-for-testing';\n\nexport interface LaunchWithExtensionOptions {\n  /** Optional command override to launch browser process */\n  command?: string;\n  /** Docker browser runtime for vm mode: 'cloakbrowser' (default) or 'chrome-for-testing' */\n  vmDockerBrowser?: VmDockerBrowserType;\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\n/** What happened when a browser's last page closed. */\nexport interface LastPageCloseOutcome {\n  /** Whether profile storage state was captured before the page went away. */\n  profileStateSaved: boolean;\n  /** Whether the browser itself was closed, as opposed to kept for reuse. */\n  browserClosed: boolean;\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  /**\n   * Apply the last-page-closed policy for a browser: save profile state and keep\n   * a profile browser open for reuse, otherwise close it. Safe to call more than\n   * once — concurrent callers share one run.\n   */\n  handleLastPageClosed(browserId: string): Promise<LastPageCloseOutcome>;\n  closeAll(): Promise<void>;\n  listBrowsers(): BrowserInstance[];\n  /** Browsers that went away without browse-tool closing them in the last 10 minutes. */\n  listRecentCrashes(): BrowserCrashRecord[];\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 an externally-launched Playwright browser so it participates in lifecycle management */\n  registerExternalBrowser(browser: Browser): string;\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/**\n * Thrown when a per-profile browser launch cannot acquire its launch slot within\n * the configured timeout because another launch for the same profile is in progress.\n */\nexport class BrowserLaunchBusyError extends Error {\n  constructor(\n    public readonly profileName: string,\n    public readonly timeoutMs: number,\n  ) {\n    super(\n      `Browser launch for profile \"${profileName}\" is busy: another launch held the profile for more than ${timeoutMs}ms. Retry shortly, or use a different profile to run in parallel.`,\n    );\n    this.name = 'BrowserLaunchBusyError';\n  }\n}\n\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 readonly CRASH_RECORD_TTL_MS = 10 * 60_000;\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 readonly profileLeaseReleases = new Map<string, () => Promise<void>>();\n  /** Browsers currently being torn down, so re-entrant closes are no-ops. */\n  private closingBrowsers: Set<string> = new Set();\n  private crashRecords: BrowserCrashRecord[] = [];\n  /** In-flight last-page handling per browser, so the work runs exactly once. */\n  private lastPageCloseHandling: Map<string, Promise<LastPageCloseOutcome>> = new Map();\n  private browserIdCounter = 0;\n  /** Per-profile launch serialization tails — prevents concurrent launches from racing on the same profile. */\n  private profileLaunchTails: Map<string, Promise<void>> = new Map();\n  private static readonly DEFAULT_PROFILE_LAUNCH_TIMEOUT_MS = 120_000;\n\n  constructor(\n    private profileService: IProfileService,\n    private pageRegistry: IPageRegistry,\n    private lockManager: IBrowserLockManager,\n    private chromeForTesting: IChromeForTestingService,\n    private readonly pageMonitor?: IPageMonitorService,\n    private readonly webSocketHub?: WebSocketHub,\n    private readonly extensionSessionRegistry?: IExtensionSessionRegistry,\n    private readonly extensionTaskQueue?: ExtensionTaskQueue,\n    private readonly browserProcessRegistry?: BrowserProcessRegistry,\n    private readonly options: BrowseOptions = {},\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    if (!options.profileName) {\n      return this.launchInternal(options);\n    }\n    return this.withProfileLaunchLock(options.profileName, () => this.launchInternal(options));\n  }\n\n  private async launchInternal(options: LaunchOptions = {}): Promise<BrowserLaunchResult> {\n    const {\n      profileName,\n      browserType = 'chromium',\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    const releaseLease =\n      profileName && !this.profileService.hasLease?.(profileName)\n        ? await this.profileService.acquireLease?.(profileName)\n        : undefined;\n    try {\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\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 settings = this.options.settings?.() ?? { fallback: false };\n      const isChromium = browserType === 'chromium';\n      let useCloak = isChromium && settings.engine === 'cloak';\n      const headless = options.headless ?? profile?.headless ?? (useCloak ? !hasDisplay() : true);\n\n      let browser: Browser | undefined;\n      let context: BrowserContext | undefined;\n      let cloakTempDir: string | undefined;\n      let warning: string | undefined;\n\n      if (useCloak) {\n        try {\n          ({ context, tempDir: cloakTempDir } = await this.launchCloakContext({\n            profileName,\n            profile,\n            headless,\n            proxy,\n            baseURL,\n            recordVideo,\n          }));\n        } catch (error) {\n          // A profile already open elsewhere is not a CloakBrowser failure; a second\n          // copy of the same identity in another engine would be worse.\n          if (!settings.fallback || error instanceof BrowserSessionConflictError) throw error;\n          const reason = error instanceof Error ? error.message : String(error);\n          warning = `CloakBrowser could not start (${reason}). Using Chrome for Testing instead, without CloakBrowser's fingerprint protection.`;\n          console.warn(`[BrowserService] ${warning}`);\n          useCloak = false;\n        }\n      }\n      if (!useCloak) {\n        const executablePath =\n          isChromium && (warning || settings.engine === 'chrome-for-testing')\n            ? await this.chromeForTesting.getExecutablePath()\n            : undefined;\n        browser = await this.launchPlaywrightBrowser(browserType, headless, proxy, executablePath);\n      }\n      const removeCloakTempDir = () => {\n        if (cloakTempDir) fs.rmSync(cloakTempDir, { recursive: true, force: true });\n      };\n\n      // Everything past this point can throw (a corrupt storage state, an invalid\n      // viewport) while the browser process is already running but not yet tracked\n      // by anything that could close it. Clean up rather than leak a Chromium.\n      try {\n        if (browser) {\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<\n                Parameters<Browser['newContext']>[0]\n              >['storageState'];\n            }\n          }\n\n          context = await browser.newContext(contextOptions);\n        }\n        if (!context) throw new Error('Browser launch produced no context');\n        // A persistent context opens with one blank page; use it instead of adding a second.\n        const page = (useCloak ? context.pages()[0] : undefined) ?? (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          ...(useCloak ? { engine: 'cloak' as const } : {}),\n          headless,\n          ownerIds: new Set(),\n        };\n\n        this.browsers.set(browserId, browserInstance);\n\n        // Clean up references when browser is manually closed (e.g., user force-quits).\n        // A persistent context has no Browser object, so its own close event stands in.\n        const onGone = () => {\n          if (!this.closingBrowsers.has(browserId)) {\n            this.recordCrash(browserInstance);\n          }\n          this.cleanupStaleBrowser(browserId, profileName);\n          removeCloakTempDir();\n          if (!this.closingBrowsers.has(browserId)) {\n            const release = this.profileLeaseReleases.get(browserId);\n            this.profileLeaseReleases.delete(browserId);\n            void release?.().catch(() => {\n              console.warn('[BrowserService] Failed to release profile lease');\n            });\n          }\n        };\n        if (browser) browser.on('disconnected', onGone);\n        else context.on('close', onGone);\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: this.makePageCloseHandler(browserInstance, onPageClose),\n        });\n\n        this.pageMonitor?.startMonitoring(pageId, page);\n        browserInstance.pageIds.add(pageId);\n        browserInstance.currentPageId = pageId;\n        this.trackPageOwner(browserInstance);\n\n        if (releaseLease) this.profileLeaseReleases.set(browserId, releaseLease);\n        return { browserInstance, pageId, page, ...(warning ? { warning } : {}) };\n      } catch (error) {\n        this.cleanupStaleBrowser(browserId, profileName);\n        try {\n          await (browser ?? context)?.close();\n        } catch {\n          // The original failure is what the caller needs to see.\n        }\n        removeCloakTempDir();\n        throw error;\n      }\n    } catch (error) {\n      await releaseLease?.();\n      throw error;\n    }\n  }\n\n  private async launchPlaywrightBrowser(\n    browserType: 'chromium' | 'firefox' | 'webkit',\n    headless: boolean,\n    proxy?: ProxyConfig,\n    executablePath?: string,\n  ): Promise<Browser> {\n    const browserTypeInstance = this.getBrowserType(browserType);\n    const proxyConfig = proxy\n      ? { proxy: { server: proxy.server, username: proxy.username, password: proxy.password, bypass: proxy.bypass } }\n      : {};\n    const requiresSandboxBypass =\n      process.platform === 'linux' && typeof process.getuid === 'function' && process.getuid() === 0;\n    const preferredChromiumExecutable =\n      browserType === 'chromium' ? executablePath || this.options.chrome?.executablePath : undefined;\n    const disableGpu = this.options.chrome?.disableGpu === true;\n    const chromiumLaunchArgs =\n      browserType === 'chromium'\n        ? [\n            ...(disableGpu\n              ? [\n                  '--disable-gpu',\n                  '--disable-software-rasterizer',\n                  '--disable-dev-shm-usage',\n                  '--disable-features=VizDisplayCompositor',\n                ]\n              : []),\n            ...(requiresSandboxBypass ? ['--no-sandbox', '--disable-setuid-sandbox'] : []),\n          ]\n        : [];\n    const chromiumLaunchOptions =\n      browserType === 'chromium'\n        ? {\n            ...(chromiumLaunchArgs.length > 0 ? { args: chromiumLaunchArgs } : {}),\n            ...(preferredChromiumExecutable ? { executablePath: preferredChromiumExecutable } : {}),\n          }\n        : {};\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        return await browserTypeInstance.launch({\n          headless,\n          ...(preferredChromiumExecutable ? {} : { channel: 'chrome' }),\n          ...chromiumLaunchOptions,\n          ...proxyConfig,\n        });\n      } catch {\n        return browserTypeInstance.launch({ headless, ...chromiumLaunchOptions, ...proxyConfig });\n      }\n    }\n    return browserTypeInstance.launch({ headless, ...chromiumLaunchOptions, ...proxyConfig });\n  }\n\n  /**\n   * Launch a CloakBrowser persistent context. The profile's user-data folder\n   * holds the fingerprint seed CloakBrowser writes, so the identity survives\n   * restarts. Without a profile a temp folder is used and removed on close.\n   */\n  private async launchCloakContext(input: {\n    profileName?: string;\n    profile: BrowserProfile | null;\n    headless: boolean;\n    proxy?: ProxyConfig;\n    baseURL?: string;\n    recordVideo?: VideoRecordingOptions;\n  }): Promise<{ context: BrowserContext; tempDir?: string }> {\n    const { profileName, profile, headless, proxy, baseURL, recordVideo } = input;\n    const userDataDir = profileName\n      ? path.join(this.profileService.getProfilesDir(), profileName, 'user-data')\n      : fs.mkdtempSync(path.join(os.tmpdir(), CLOAK_USER_DATA_PREFIX));\n    const tempDir = profileName ? undefined : userDataDir;\n    const firstRun = !fs.existsSync(userDataDir);\n    let context: BrowserContext | undefined;\n    try {\n      if (!firstRun) await this.lockManager.ensureAvailable(userDataDir);\n      fs.mkdirSync(userDataDir, { recursive: true, mode: 0o700 });\n\n      // Lazy: only the cloak engine pays for loading the wrapper.\n      const { launchPersistentContext } = await import('cloakbrowser');\n      // Never pass userAgent, locale or timezone as context options: CloakBrowser\n      // keeps the fingerprint coherent and sets locale/timezone via binary flags.\n      context = await launchPersistentContext({\n        userDataDir,\n        headless,\n        ...(proxy\n          ? {\n              proxy: { server: proxy.server, username: proxy.username, password: proxy.password, bypass: proxy.bypass },\n            }\n          : {}),\n        ...(profile?.locale ? { locale: profile.locale } : {}),\n        ...(profile?.timezone ? { timezone: profile.timezone } : {}),\n        ...(profile?.geoip ? { geoip: true } : {}),\n        ...(profile?.viewport ? { viewport: profile.viewport } : {}),\n        ...(profile?.colorScheme ? { colorScheme: profile.colorScheme } : {}),\n        contextOptions: {\n          ...(baseURL ? { baseURL } : {}),\n          ...(recordVideo ? { recordVideo } : {}),\n          ...(profile?.geolocation ? { geolocation: profile.geolocation } : {}),\n          ...(profile?.permissions ? { permissions: profile.permissions } : {}),\n        },\n      });\n\n      // One-time import: a profile used by the playwright engine before keeps its login.\n      if (firstRun && profileName) {\n        const state = await this.profileService.loadStorageState(profileName);\n        const cookies = Array.isArray(state?.cookies) ? state.cookies : [];\n        if (cookies.length > 0) {\n          await context.addCookies(cookies as Parameters<BrowserContext['addCookies']>[0]);\n        }\n      }\n      return { context, tempDir };\n    } catch (error) {\n      await context?.close().catch(() => undefined);\n      if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true });\n      throw error;\n    }\n  }\n\n  /**\n   * Build the page-close callback shared by every Playwright page.\n   *\n   * Keeping one implementation matters: the last-page policy lives in\n   * {@link handleLastPageClosed}, and a second copy would drift away from it.\n   */\n  private makePageCloseHandler(\n    browserInstance: BrowserInstance,\n    onPageClose?: (pageId: string) => void,\n  ): (pageId: string) => void {\n    return (pageId: string) => {\n      browserInstance.pageIds.delete(pageId);\n      if (browserInstance.currentPageId === pageId) {\n        const remaining = Array.from(browserInstance.pageIds);\n        browserInstance.currentPageId = remaining.length > 0 ? remaining[0] : null;\n      }\n      onPageClose?.(pageId);\n\n      if (browserInstance.pageIds.size === 0) {\n        void this.handleLastPageClosed(browserInstance.id).catch((error) => {\n          // Not fatal: IdleCleanupService reaps zero-page browsers as a backstop.\n          console.warn(`[BrowserService] Last-page handling failed for \"${browserInstance.id}\":`, error);\n        });\n      }\n    };\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    if (!options.profileName) {\n      return this.launchWithExtensionInternal(options);\n    }\n    return this.withProfileLaunchLock(options.profileName, () => this.launchWithExtensionInternal(options));\n  }\n\n  private async launchWithExtensionInternal(\n    options: LaunchWithExtensionOptions = {},\n  ): 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      vmDockerBrowser: options.vmDockerBrowser,\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 = this.options.chrome?.autoSelectTabCaptureSourceTitle?.trim();\n    const autoSelectDesktopCaptureSource = this.options.chrome?.autoSelectDesktopCaptureSource?.trim();\n    const useFakeUiForMediaStream = this.options.chrome?.useFakeUiForMediaStream === true;\n\n    // Chrome args for stealth mode - NO CDP, NO automation flags\n    const requiresSandboxBypass =\n      vmRuntime.kind === 'docker' ||\n      (process.platform === 'linux' && typeof process.getuid === 'function' && process.getuid() === 0);\n\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      ...(vmRuntime.kind === 'docker' ? ['--disable-gpu'] : []),\n      '--silent-debugger-extension-api',\n      '--password-store=basic',\n      '--use-mock-keychain',\n      '--disable-dev-shm-usage',\n      '--webrtc-ip-handling-policy=disable_non_proxied_udp',\n      '--enforce-webrtc-ip-permission-check',\n      `--lang=${process.env.LANG?.split('.')[0]?.replace('_', '-') || 'en-US'}`,\n      ...(requiresSandboxBypass ? ['--no-sandbox', '--disable-setuid-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 dockerEnvWithDefaults =\n      vmRuntime.kind === 'docker'\n        ? {\n            TZ: Intl.DateTimeFormat().resolvedOptions().timeZone,\n            ...options.dockerEnv,\n          }\n        : options.dockerEnv;\n\n    const spawnArgs =\n      vmRuntime.kind === 'docker'\n        ? [\n            ...this.buildDockerSpawnArgs(\n              vmRuntime.docker,\n              userDataDir,\n              dockerVolumeArgs,\n              options.dockerRunArgs,\n              dockerEnvWithDefaults,\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    vmDockerBrowser?: VmDockerBrowserType;\n    extensionHostPath: string;\n    requestedRuntimeExtensionPath?: string;\n    userDataDir: string;\n  }): Promise<ResolvedVmRuntime> {\n    const { command, launchMode, vmDockerBrowser, extensionHostPath, requestedRuntimeExtensionPath, userDataDir } =\n      input;\n\n    if (launchMode === 'vm' && this.isDockerVmAlias(command)) {\n      const extensionPathInContainer = this.resolveDockerRuntimeExtensionPath(requestedRuntimeExtensionPath);\n      const userDataDirInContainer = this.dockerOptions().userDataDir || DEFAULT_VM_DOCKER_USER_DATA_DIR;\n      const dockerBrowserType = this.resolveDockerBrowserType(vmDockerBrowser);\n      const { image, browserBinary, platform } = this.resolveDockerImageConfig(dockerBrowserType);\n\n      if (dockerBrowserType === 'chrome-for-testing') {\n        await ensureManagedDockerChromeForTestingImageAvailable({\n          image,\n          platform,\n          version: this.options.chrome?.chromeForTestingVersion,\n        });\n      } else {\n        await ensureCloakBrowserImageAvailable({ image, platform });\n      }\n\n      return {\n        kind: 'docker',\n        command: 'docker',\n        extensionPath: extensionPathInContainer,\n        userDataDir: userDataDirInContainer,\n        docker: {\n          image,\n          browserBinary,\n          platform,\n          extensionPathInContainer,\n          userDataDirInContainer,\n        },\n      };\n    }\n\n    const launchCommand = await this.resolveLaunchCommand({\n      command,\n      launchMode,\n      vmDockerBrowser,\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 this.dockerOptions().extensionPath || DEFAULT_VM_DOCKER_EXTENSION_PATH;\n  }\n\n  private dockerOptions(): BrowseVmDockerOptions {\n    return this.options.vm?.docker ?? {};\n  }\n\n  private resolveDockerBrowserType(explicit?: VmDockerBrowserType): VmDockerBrowserType {\n    const envBrowser = this.dockerOptions().browser?.trim().toLowerCase();\n    if (envBrowser === 'chrome-for-testing' || envBrowser === 'cft') return 'chrome-for-testing';\n    if (envBrowser === 'cloakbrowser' || envBrowser === 'cloak') return 'cloakbrowser';\n\n    if (explicit) return explicit;\n    if (this.dockerOptions().image) return 'chrome-for-testing';\n\n    return 'cloakbrowser';\n  }\n\n  private resolveDockerImageConfig(browserType: VmDockerBrowserType): {\n    image: string;\n    browserBinary: string;\n    platform: string;\n  } {\n    const docker = this.dockerOptions();\n    if (browserType === 'chrome-for-testing') {\n      return {\n        image: docker.image || DEFAULT_VM_DOCKER_IMAGE,\n        browserBinary: docker.browserBinary || DEFAULT_VM_DOCKER_BROWSER_BINARY,\n        platform: docker.platform || chromeForTestingConfig.dockerPlatform,\n      };\n    }\n\n    return {\n      image: docker.image || CLOAKBROWSER_DOCKER_IMAGE,\n      browserBinary: docker.browserBinary || CLOAKBROWSER_DOCKER_BROWSER_BINARY,\n      platform: docker.platform || cloakBrowserConfig.dockerPlatform,\n    };\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      this.dockerOptions().shmSize || DEFAULT_VM_DOCKER_SHM_SIZE,\n    ];\n\n    if (dockerConfig.platform) {\n      args.push('--platform', dockerConfig.platform);\n    }\n\n    const network = this.dockerOptions().network;\n    if (network) {\n      args.push('--network', 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(this.dockerOptions().runArgs));\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      'command -v dbus-daemon >/dev/null 2>&1 && export DBUS_SESSION_BUS_ADDRESS=\"$(dbus-daemon --session --fork --print-address --nopidfile)\" || true',\n      'if command -v pulseaudio >/dev/null 2>&1; then pulseaudio --check >/dev/null 2>&1 || pulseaudio --daemonize=yes --exit-idle-time=-1 --disable-shm=true --log-target=stderr >/tmp/pulseaudio.log 2>&1; export PULSE_SERVER=\"unix:$XDG_RUNTIME_DIR/pulse/native\"; fi',\n      `Xvfb :99 -screen 0 ${DEFAULT_VM_XVFB_SCREEN_SIZE} -nolisten tcp >/tmp/xvfb.log 2>&1 &`,\n      'if command -v xdpyinfo >/dev/null 2>&1; then for i in $(seq 1 30); do xdpyinfo -display :99 >/dev/null 2>&1 && break; sleep 0.2; done; else sleep 1; fi',\n      'export DISPLAY=:99',\n      `BROWSER_BIN=${quotedBrowserBinary}`,\n      'exec \"$BROWSER_BIN\" \"$@\"',\n    ];\n\n    if (options.enableVnc) {\n      // These `${...}` are shell parameter expansions evaluated by `bash -lc`\n      // inside the container, not JavaScript interpolation.\n      // biome-ignore-start lint/suspicious/noTemplateCurlyInString: shell script, not a JS template\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      // biome-ignore-end lint/suspicious/noTemplateCurlyInString: shell script, not a JS template\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    vmDockerBrowser?: VmDockerBrowserType;\n  }): Promise<string> {\n    const { command, launchMode, vmDockerBrowser } = input;\n\n    if (launchMode === 'vm' && vmDockerBrowser === 'chrome-for-testing') {\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 an explicitly provided or configured path\n    for (const candidate of [providedPath, this.options.extensionPath]) {\n      if (candidate && fs.existsSync(path.join(candidate, 'manifest.json'))) {\n        return candidate;\n      }\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    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/po-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 (error) {\n      if (this.options.chrome?.chromeForTestingPath?.trim()) {\n        throw error;\n      }\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 && browserInstance.engine !== 'cloak')) {\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: this.makePageCloseHandler(browserInstance, onClose),\n    });\n\n    this.pageMonitor?.startMonitoring(pageId, page);\n    browserInstance.pageIds.add(pageId);\n    this.trackPageOwner(browserInstance);\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  /**\n   * Closes only the pages owned by `ownerId` in a shared browser, leaving the\n   * browser (and other owners' pages) intact. Used when one session disconnects\n   * while a concurrent agent is still using the same profile's browser.\n   */\n  private async releaseOwnerPages(browserId: string, ownerId: string): Promise<void> {\n    const ownerPages = this.pageRegistry.findByOwner(ownerId).filter((entry) => entry.browserId === browserId);\n    for (const entry of ownerPages) {\n      try {\n        if (entry.page) {\n          await entry.page.close();\n        } else {\n          this.pageRegistry.remove(entry.id);\n        }\n      } catch {\n        // Best-effort: ignore individual page close failures during release.\n      }\n    }\n  }\n\n  async closeBrowser(id: string): Promise<void> {\n    if (this.closingBrowsers.has(id)) {\n      return;\n    }\n    this.closingBrowsers.add(id);\n    try {\n      await this.closeBrowserInternal(id);\n      if (!this.browsers.has(id)) {\n        const release = this.profileLeaseReleases.get(id);\n        this.profileLeaseReleases.delete(id);\n        await release?.();\n      }\n    } finally {\n      this.closingBrowsers.delete(id);\n    }\n  }\n\n  /**\n   * Drop everything keyed to a browser that no longer exists.\n   *\n   * Every teardown path funnels through here and {@link cleanupStaleBrowser}, so\n   * this is the one place that has to remember page entries and extension task\n   * state. Leaving either behind misroutes later calls to a dead browser.\n   */\n  private releaseBrowserResources(browserId: string): void {\n    const pageIds = this.pageRegistry.findByBrowser(browserId).map((entry) => entry.id);\n    for (const pageId of pageIds) {\n      this.pageRegistry.remove(pageId);\n    }\n\n    this.extensionTaskQueue?.purgeTargets([browserId, ...pageIds], `Browser \"${browserId}\" closed`);\n  }\n\n  /**\n   * Decide what happens once a browser's last page closes.\n   *\n   * This is the single owner of that policy: the Playwright close event fires\n   * before `page.close()` resolves, so a tool doing its own save-then-close\n   * would race this handler. Callers await the same outcome instead.\n   */\n  async handleLastPageClosed(browserId: string): Promise<LastPageCloseOutcome> {\n    const inFlight = this.lastPageCloseHandling.get(browserId);\n    if (inFlight) {\n      return inFlight;\n    }\n\n    const browserInstance = this.browsers.get(browserId);\n    if (!browserInstance || browserInstance.pageIds.size > 0 || this.closingBrowsers.has(browserId)) {\n      return { profileStateSaved: false, browserClosed: false };\n    }\n\n    const handling = this.runLastPageCloseHandling(browserInstance);\n    this.lastPageCloseHandling.set(browserId, handling);\n\n    try {\n      return await handling;\n    } finally {\n      this.lastPageCloseHandling.delete(browserId);\n    }\n  }\n\n  private async runLastPageCloseHandling(browserInstance: BrowserInstance): Promise<LastPageCloseOutcome> {\n    const browserId = browserInstance.id;\n\n    // Extension and VM browsers keep their state in a persistent user-data\n    // directory, so there is nothing to snapshot and nothing to keep open for.\n    if (browserInstance.mode !== 'playwright' || !browserInstance.profileName || !browserInstance.context) {\n      await this.closeBrowser(browserId);\n      return { profileStateSaved: false, browserClosed: true };\n    }\n\n    // A cloak identity already persists everything in its user-data folder. It\n    // stays open warm like any profile browser; idle cleanup reaps it.\n    if (browserInstance.engine === 'cloak') {\n      return { profileStateSaved: false, browserClosed: false };\n    }\n    let profileStateSaved = false;\n    try {\n      const storageState = await browserInstance.context.storageState();\n      await this.profileService.saveStorageState(browserInstance.profileName, storageState);\n      profileStateSaved = true;\n    } catch (error) {\n      console.warn(`[BrowserService] Failed to save profile state for \"${browserInstance.profileName}\":`, error);\n    }\n\n    // A profile browser stays open so the next task can reuse its warm session.\n    // It is not orphaned: IdleCleanupService reaps page-less browsers on the\n    // shorter page timeout.\n    return { profileStateSaved, browserClosed: false };\n  }\n\n  private async closeBrowserInternal(id: string): Promise<void> {\n    const ownerId = getCurrentOwnerId();\n    if (ownerId && this.browsers.get(id)) {\n      const otherOwnersHavePages = this.pageRegistry.findByBrowser(id).some((entry) => entry.ownerId !== ownerId);\n      if (otherOwnersHavePages) {\n        // Another concurrent agent still has pages in this shared profile browser.\n        // Release only this owner's pages and keep the browser alive for the others.\n        await this.releaseOwnerPages(id, ownerId);\n        return;\n      }\n    }\n\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      this.releaseBrowserResources(id);\n      await this.browserProcessRegistry?.releaseBrowserProcess(id).catch(() => {\n        // Registry cleanup is best-effort after the browser process is gone.\n      });\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  listRecentCrashes(): BrowserCrashRecord[] {\n    this.pruneCrashRecords();\n    return this.crashRecords.map((record) => ({ ...record, ownerIds: [...record.ownerIds] }));\n  }\n\n  /** Remember the ambient owner, the same one PageRegistry stamps on the page. */\n  private trackPageOwner(browserInstance: BrowserInstance): void {\n    const ownerId = getCurrentOwnerId();\n    if (ownerId) {\n      browserInstance.ownerIds?.add(ownerId);\n    }\n  }\n\n  /** Called before cleanup, which drops the page entries the owners could be read from. */\n  private recordCrash(browserInstance: BrowserInstance): void {\n    this.pruneCrashRecords();\n    this.crashRecords.push({\n      browserId: browserInstance.id,\n      profileName: browserInstance.profileName,\n      headless: browserInstance.headless,\n      ownerIds: [...(browserInstance.ownerIds ?? [])],\n      at: Date.now(),\n    });\n  }\n\n  private pruneCrashRecords(): void {\n    const cutoff = Date.now() - BrowserService.CRASH_RECORD_TTL_MS;\n    this.crashRecords = this.crashRecords.filter((record) => record.at >= cutoff);\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  registerExternalBrowser(browser: Browser): string {\n    const browserId = `browser-${++this.browserIdCounter}`;\n    const now = new Date();\n\n    const browserInstance: BrowserInstance = {\n      id: browserId,\n      mode: 'playwright',\n      browser,\n      context: undefined,\n      pageIds: new Set(),\n      currentPageId: null,\n      createdAt: now,\n      lastAccessedAt: now,\n      specOrigin: true,\n    };\n\n    this.browsers.set(browserId, browserInstance);\n\n    browser.on('disconnected', () => {\n      this.cleanupStaleBrowser(browserId);\n    });\n\n    return browserId;\n  }\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    void this.hydrateExtensionBrowserFromRegistry(browserId);\n  }\n\n  private async hydrateExtensionBrowserFromRegistry(browserId: string): Promise<void> {\n    if (!this.browserProcessRegistry) {\n      return;\n    }\n\n    const record = await this.browserProcessRegistry.findBrowserProcess(browserId).catch(() => undefined);\n    if (!record) {\n      return;\n    }\n\n    const metadata = this.browserProcessRegistry.parseMetadata(record);\n    const browserInstance = this.browsers.get(browserId);\n    if (!metadata || !browserInstance) {\n      return;\n    }\n\n    browserInstance.pid = record.pid;\n    browserInstance.profileName = metadata.profileName;\n    browserInstance.userDataDir = metadata.userDataDir;\n    if (metadata.mode) {\n      browserInstance.mode = metadata.mode;\n    }\n    if (metadata.profileName) {\n      this.profileToBrowserId.set(metadata.profileName, browserId);\n    }\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          await browserOutputDirectory(),\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(this.options.chrome?.chromeForTestingVersion);\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        // A cloak context is removed from the map by its close handler, so presence means alive.\n        if (browserInstance.engine === 'cloak') return browserInstance.context !== undefined;\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 userDataDir = vmRuntime.userDataDir;\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      userDataDir,\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.browserProcessRegistry\n        ?.registerBrowserProcess({\n          browserId,\n          profileName,\n          userDataDir,\n          pid: chromeProcess.pid,\n          command: vmRuntime.command,\n          args: spawnArgs,\n          serverPid: process.pid,\n          serverPort: this.options.registry?.port,\n          ownerId: getCurrentOwnerId(),\n          mode: launchMode,\n        })\n        .catch((error) => {\n          // Registry tracking is best-effort; never fail a working launch on it.\n          console.warn(`[BrowserService] Failed to register browser process \"${browserId}\":`, error);\n        });\n\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: ${String(exitCode)}` : undefined,\n      exitSignal ? `Signal: ${String(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 getProfileLaunchTimeoutMs(): number {\n    const configured = this.options.timeouts?.profileLaunchMs;\n    return configured !== undefined && Number.isFinite(configured) && configured > 0\n      ? configured\n      : BrowserService.DEFAULT_PROFILE_LAUNCH_TIMEOUT_MS;\n  }\n\n  /**\n   * Serializes launches that target the same profile so concurrent callers cannot\n   * race on profileToBrowserId or the Chromium user-data-dir. The first caller\n   * creates the browser; later callers observe it and reuse it (a new page). Waiting\n   * on predecessors is bounded by the configured timeout, after which a\n   * BrowserLaunchBusyError is thrown so the caller can retry or pick another profile.\n   */\n  private async withProfileLaunchLock<T>(profileName: string, operation: () => Promise<T>): Promise<T> {\n    const predecessor = this.profileLaunchTails.get(profileName) ?? Promise.resolve();\n    let releaseMine: (() => void) | undefined;\n    const mine = new Promise<void>((resolve) => {\n      releaseMine = resolve;\n    });\n    const myTail = predecessor.then(() => mine);\n    this.profileLaunchTails.set(profileName, myTail);\n\n    const timeoutMs = this.getProfileLaunchTimeoutMs();\n    const timedOut = await this.didTimeout(predecessor, timeoutMs);\n    if (timedOut) {\n      // Release our slot so queued successors still advance behind the slow predecessor,\n      // but do not prune the chain — the predecessor still holds the profile.\n      releaseMine?.();\n      throw new BrowserLaunchBusyError(profileName, timeoutMs);\n    }\n\n    try {\n      return await operation();\n    } finally {\n      releaseMine?.();\n      if (this.profileLaunchTails.get(profileName) === myTail) {\n        this.profileLaunchTails.delete(profileName);\n      }\n    }\n  }\n\n  private async didTimeout(promise: Promise<void>, timeoutMs: number): Promise<boolean> {\n    let timer: ReturnType<typeof setTimeout> | undefined;\n    const timeout = new Promise<boolean>((resolve) => {\n      timer = setTimeout(() => resolve(true), timeoutMs);\n    });\n    try {\n      return await Promise.race([promise.then(() => false).catch(() => false), timeout]);\n    } finally {\n      if (timer) {\n        clearTimeout(timer);\n      }\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    this.browserProcessRegistry?.releaseBrowserProcess(browserId).catch(() => {\n      // Ignore registry cleanup failures during stale browser cleanup.\n    });\n    if (profileName) {\n      this.profileToBrowserId.delete(profileName);\n    }\n    // Playwright pages clean themselves up through their close handlers, but\n    // extension pages have no such event and would otherwise keep resolving to\n    // this dead browser until the idle reaper notices.\n    this.releaseBrowserResources(browserId);\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, readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { transform } from 'esbuild';\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 { code } = await transform(source, { loader: 'ts', format: 'esm', target: 'node20' });\n  const moduleUrl = `data:text/javascript;base64,${Buffer.from(code, 'utf8').toString('base64')}`;\n  return (await import(moduleUrl)) as LoadedSnippetModule;\n}\n\nexport class CodeSnippetService implements ICodeSnippetService {\n  constructor(private readonly snippetsDir?: string) {}\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 * - Constructor injection; createBrowseRuntime wires the instances\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 type { FrameLocator, Locator, Page } from 'playwright';\nimport { z } from 'zod';\n\nimport { DEFAULT_LOCATE_TIMEOUT_MS, DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.js';\n\ninterface SelectorShortcut {\n  kind: 'text' | 'hasText';\n  text: string;\n  tagName?: string;\n  exact: boolean;\n}\n\nfunction unquoteSelectorText(rawText: string): { text: string; exact: boolean } {\n  const text = rawText.trim();\n  const quote = text[0];\n  if ((quote === '\"' || quote === \"'\") && text.endsWith(quote)) {\n    return { text: text.slice(1, -1), exact: true };\n  }\n  return { text, exact: false };\n}\n\nfunction parseSelectorShortcut(selector: string): SelectorShortcut | null {\n  const trimmed = selector.trim();\n\n  if (trimmed.startsWith('text=')) {\n    const parsed = unquoteSelectorText(trimmed.slice('text='.length));\n    return { kind: 'text', text: parsed.text, exact: parsed.exact };\n  }\n\n  const hasTextMatch = trimmed.match(/^([a-zA-Z][\\w-]*)?:has-text\\((.*)\\)$/);\n  if (hasTextMatch) {\n    const parsed = unquoteSelectorText(hasTextMatch[2] ?? '');\n    return {\n      kind: 'hasText',\n      tagName: hasTextMatch[1],\n      text: parsed.text,\n      exact: false,\n    };\n  }\n\n  return null;\n}\n\n/**\n * Selector specification for locating elements.\n * Supports semantic selectors first, then CSS/XPath fallback.\n */\nexport interface ElementSelector {\n  /** Accessibility snapshot UID reference */\n  uid?: string;\n  /** ARIA role (e.g., \"button\", \"textbox\") */\n  role?: string;\n  /** Accessible name used with the role selector */\n  name?: string;\n  /** Associated label text */\n  label?: string;\n  /** Placeholder text */\n  placeholder?: string;\n  /** data-testid value */\n  testId?: string;\n  /** Visible text content */\n  text?: string;\n  /** Exact-match flag for semantic text based selectors */\n  exact?: boolean;\n  /** CSS selector (e.g., \"button.submit\", \"#login-form\") */\n  selector?: string;\n  /** XPath expression (e.g., \"//button[@type='submit']\") */\n  xpath?: string;\n  /** Frame selector for iframe content (CSS or name) */\n  frame?: string;\n}\n\nexport const ElementSelectorSchema = z.object({\n  uid: z.string().optional().describe('Accessibility snapshot UID reference'),\n  role: z.string().optional().describe('ARIA role (e.g., \"button\", \"textbox\")'),\n  name: z.string().optional().describe('Accessible name used with the role selector'),\n  label: z.string().optional().describe('Associated label text'),\n  placeholder: z.string().optional().describe('Placeholder text'),\n  testId: z.string().optional().describe('data-testid value'),\n  text: z.string().optional().describe('Visible text content'),\n  exact: z.boolean().optional().default(false).describe('Use exact matching for semantic selectors'),\n  selector: z.string().optional().describe('CSS selector'),\n  xpath: z.string().optional().describe('XPath expression'),\n  frame: z.string().optional().describe('Frame selector'),\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\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    // Wait briefly for late-rendering elements instead of failing on the first empty count.\n    const first = locator.first();\n    try {\n      await first.waitFor({ state: 'attached', timeout: DEFAULT_LOCATE_TIMEOUT_MS });\n    } catch {\n      throw new Error(`Element not found: ${this.describeSelector(spec)}`);\n    }\n\n    return 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 | FrameLocator, spec: ElementSelector): Locator {\n    const exact = spec.exact ?? false;\n\n    if (spec.uid) {\n      return context.locator(\n        `[data-pw-proxy=\"${spec.uid}\"], [data-browse-tool-uid=\"${spec.uid}\"], [data-uid=\"${spec.uid}\"]`,\n      );\n    }\n\n    if (spec.role) {\n      const role = spec.role as Parameters<Page['getByRole']>[0];\n      return spec.name ? context.getByRole(role, { name: spec.name, exact }) : context.getByRole(role);\n    }\n\n    if (spec.label) {\n      return context.getByLabel(spec.label, { exact });\n    }\n\n    if (spec.placeholder) {\n      return context.getByPlaceholder(spec.placeholder, { exact });\n    }\n\n    if (spec.testId) {\n      return context.getByTestId(spec.testId);\n    }\n\n    if (spec.text) {\n      return context.getByText(spec.text, { exact });\n    }\n\n    if (spec.selector) {\n      if (spec.selector.trim().startsWith('uid=')) {\n        throw new Error(\n          `UID selectors must use the uid field, for example {\"uid\":\"${spec.selector.trim().slice(4)}\"}. Snapshot UIDs are temporary, so take a new snapshot before retrying if it no longer resolves.`,\n        );\n      }\n      const shortcut = parseSelectorShortcut(spec.selector);\n      if (shortcut?.kind === 'text') {\n        return context.getByText(shortcut.text, { exact: shortcut.exact });\n      }\n      if (shortcut?.kind === 'hasText') {\n        return context.locator(shortcut.tagName ?? '*').filter({ hasText: shortcut.text });\n      }\n      return context.locator(spec.selector);\n    }\n\n    if (spec.xpath) {\n      return context.locator(`xpath=${spec.xpath}`);\n    }\n\n    throw new Error('No selector provided. Specify one of: uid, role, label, placeholder, text, selector, or xpath.');\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.role) parts.push(`role=\"${spec.role}\"`);\n    if (spec.name) parts.push(`name=\"${spec.name}\"`);\n    if (spec.label) parts.push(`label=\"${spec.label}\"`);\n    if (spec.placeholder) parts.push(`placeholder=\"${spec.placeholder}\"`);\n    if (spec.testId) parts.push(`testId=\"${spec.testId}\"`);\n    if (spec.text) parts.push(`text=\"${spec.text}\"`);\n    if (spec.exact) parts.push('exact=true');\n    if (spec.selector) parts.push(`selector=\"${spec.selector}\"`);\n    if (spec.xpath) parts.push(`xpath=\"${spec.xpath}\"`);\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 { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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  /** Offset from the element's top-left corner */\n  position?: { x: number; y: number };\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\nexport interface IExtensionElementHandleProxy {\n  asElement(): IExtensionElementHandleProxy | null;\n  evaluate<T>(fn: string | ((element: Element, ...args: unknown[]) => T), ...args: unknown[]): Promise<T>;\n  setInputFiles(files: string | string[]): Promise<void>;\n  dispose(): Promise<void>;\n}\n\nconst EXTENSION_PROXY_DEBUG_ENABLED = process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS === '1';\nconst ELEMENT_HANDLE_MARKER_ATTR = 'data-browse-tool-handle-id';\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  evaluateHandle<T>(\n    fn: string | ((...args: unknown[]) => T),\n    ...args: unknown[]\n  ): Promise<IExtensionElementHandleProxy>;\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>; newCDPSession: () => Promise<never> };\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\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(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    this.currentUrl = 'about:blank';\n    void this.currentUrlAsync().catch(() => {\n      /* Best-effort refresh only */\n    });\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      position: options?.position,\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      format: options?.type,\n    });\n\n    if (!result.success) {\n      throw new Error(result.error ?? 'Screenshot failed');\n    }\n\n    const screenshotResult = result.result as\n      | { content?: Array<{ type?: string; data?: string; mimeType?: string }> }\n      | undefined;\n    const firstContent = screenshotResult?.content?.[0];\n    if (options?.path && firstContent?.type === 'image' && typeof firstContent.data === 'string') {\n      mkdirSync(dirname(options.path), { recursive: true });\n      writeFileSync(options.path, Buffer.from(firstContent.data, 'base64'));\n      return options.path;\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   * Returns the latest cached value. Call currentUrlAsync() to refresh it.\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   * Evaluate a page function and return a small element-handle proxy.\n   * Extension mode cannot keep real Playwright JSHandles across the server boundary,\n   * so DOM element results are marked in-page and later resolved by that marker.\n   */\n  async evaluateHandle<T>(\n    fn: string | ((...args: unknown[]) => T),\n    ...args: unknown[]\n  ): Promise<IExtensionElementHandleProxy> {\n    const markerValue = `handle-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n    const functionSource = typeof fn === 'function' ? fn.toString() : fn;\n    const argsJson = JSON.stringify(args);\n    const markerAttrJson = JSON.stringify(ELEMENT_HANDLE_MARKER_ATTR);\n    const markerValueJson = JSON.stringify(markerValue);\n\n    const result = await this.evaluate<{ element: boolean }>(`async () => {\n      const pageFunctionOrValue = (${functionSource});\n      const args = ${argsJson};\n      const value = typeof pageFunctionOrValue === 'function'\n        ? await pageFunctionOrValue(...args)\n        : pageFunctionOrValue;\n      if (!(value instanceof Element)) {\n        return { element: false };\n      }\n      value.setAttribute(${markerAttrJson}, ${markerValueJson});\n      return { element: true };\n    }`);\n\n    return new ExtensionElementHandleProxy(this, ELEMENT_HANDLE_MARKER_ATTR, markerValue, Boolean(result?.element));\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>; newCDPSession: () => Promise<never> } {\n    return {\n      clearCookies: async () => {\n        await this.clearStorageState();\n      },\n      newCDPSession: async () => {\n        throw new Error(\n          'context.newCDPSession is unavailable in Chrome extension mode. Use the built-in browser network, console, evaluate, and emulation tools instead.',\n        );\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  async setInputFilesBySelector(selector: string, files: string | string[]): Promise<void> {\n    const result = await this.executeTask('browser_upload_file', {\n      selector,\n      files,\n    });\n\n    if (!result.success) {\n      throw new Error(result.error ?? `Set input files failed on ${selector}`);\n    }\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\nclass ExtensionElementHandleProxy implements IExtensionElementHandleProxy {\n  constructor(\n    private readonly page: ExtensionPageProxy,\n    private readonly markerAttr: string,\n    private readonly markerValue: string,\n    private readonly isElement: boolean,\n  ) {}\n\n  asElement(): IExtensionElementHandleProxy | null {\n    return this.isElement ? this : null;\n  }\n\n  async evaluate<T>(fn: string | ((element: Element, ...args: unknown[]) => T), ...args: unknown[]): Promise<T> {\n    this.assertElement();\n\n    const functionSource = typeof fn === 'function' ? fn.toString() : fn;\n    const argsJson = JSON.stringify(args);\n\n    return this.page.evaluate<T>(`async () => {\n      ${this.buildElementLookupScript()}\n      const element = findMarkedElement();\n      if (!element) {\n        throw new Error('Extension element handle is no longer attached');\n      }\n      const pageFunction = (${functionSource});\n      const args = ${argsJson};\n      return await pageFunction(element, ...args);\n    }`);\n  }\n\n  async setInputFiles(files: string | string[]): Promise<void> {\n    this.assertElement();\n    await this.page.setInputFilesBySelector(`[${this.markerAttr}=\"${this.markerValue}\"]`, files);\n  }\n\n  async dispose(): Promise<void> {\n    if (!this.isElement) {\n      return;\n    }\n\n    await this.page.evaluate<void>(`() => {\n      ${this.buildElementLookupScript()}\n      findMarkedElement()?.removeAttribute(${JSON.stringify(this.markerAttr)});\n    }`);\n  }\n\n  private assertElement(): void {\n    if (!this.isElement) {\n      throw new Error('Extension handle does not reference a DOM element');\n    }\n  }\n\n  private buildElementLookupScript(): string {\n    return `\n      const collectRoots = (root, roots = []) => {\n        roots.push(root);\n        root.querySelectorAll('*').forEach((element) => {\n          if (element.shadowRoot) collectRoots(element.shadowRoot, roots);\n        });\n        return roots;\n      };\n      const findMarkedElement = () => {\n        const selector = ${JSON.stringify(`[${this.markerAttr}=\"${this.markerValue}\"]`)};\n        for (const root of collectRoots(document)) {\n          const element = root.querySelector(selector);\n          if (element) return element;\n        }\n        return null;\n      };\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 { randomUUID } from 'node:crypto';\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\nexport class ExtensionSessionRegistry implements IExtensionSessionRegistry {\n  private sessions = new Map<string, ExtensionSession>();\n  private browserIdToSessionId = new Map<string, string>();\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    // Unguessable: a session id identifies a connected extension, so a\n    // predictable counter would let anything that can reach the daemon address\n    // someone else's session.\n    const sessionId = `session-${randomUUID()}`;\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","import { copyFile, mkdir, writeFile } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\n\nimport { browserOutputDirectory } from '../utils/ownerContext.js';\n\nexport interface RunSpecAttachment {\n  name: string;\n  contentType: string;\n  path: string;\n}\n\nexport interface RunSpecAttachmentOptions {\n  body?: Buffer | string;\n  path?: string;\n  contentType?: string;\n}\n\nexport interface RunSpecTestInfo {\n  readonly title: string;\n  readonly attachments: readonly RunSpecAttachment[];\n  attach(name: string, options: RunSpecAttachmentOptions): Promise<void>;\n}\n\nexport interface RunSpecTestInfoAdapter {\n  testInfo: RunSpecTestInfo;\n  getAttachments(): RunSpecAttachment[];\n}\n\nconst CONTENT_TYPE_EXTENSIONS: Record<string, string> = {\n  'application/json': '.json',\n  'image/jpeg': '.jpg',\n  'image/png': '.png',\n  'text/html': '.html',\n  'text/plain': '.txt',\n};\n\nfunction sanitizePathSegment(value: string): string {\n  const sanitized = value\n    .trim()\n    .replace(/[^a-zA-Z0-9._-]+/g, '-')\n    .replace(/^-+|-+$/g, '');\n  return sanitized || 'attachment';\n}\n\nfunction attachmentExtension(name: string, options: RunSpecAttachmentOptions): string {\n  const nameExtension = extname(name);\n  if (nameExtension) {\n    return nameExtension;\n  }\n\n  if (options.path) {\n    const pathExtension = extname(options.path);\n    if (pathExtension) {\n      return pathExtension;\n    }\n  }\n\n  return CONTENT_TYPE_EXTENSIONS[options.contentType ?? ''] ?? '.bin';\n}\n\nfunction validateAttachment(name: string, options: RunSpecAttachmentOptions): void {\n  if (!name.trim()) {\n    throw new Error('RUN_SPEC_INVALID_ATTACHMENT: attachment name must not be empty');\n  }\n\n  const hasBody = options.body !== undefined;\n  const hasPath = options.path !== undefined;\n  if (hasBody === hasPath) {\n    throw new Error('RUN_SPEC_INVALID_ATTACHMENT: provide exactly one of body or path');\n  }\n}\n\nexport function createRunSpecTestInfo(sessionId: string, title: string): RunSpecTestInfoAdapter {\n  const attachments: RunSpecAttachment[] = [];\n  const supported: RunSpecTestInfo = {\n    title,\n    get attachments() {\n      return [...attachments];\n    },\n    async attach(name, options) {\n      validateAttachment(name, options);\n      const outputDirectory = join(\n        await browserOutputDirectory(),\n        'browse-tool-attachments',\n        sanitizePathSegment(sessionId),\n        sanitizePathSegment(title),\n      );\n      await mkdir(outputDirectory, { recursive: true, mode: 0o700 });\n\n      const extension = attachmentExtension(name, options);\n      const rawBaseName = extname(name) ? name.slice(0, -extname(name).length) : name;\n      const outputPath = join(\n        outputDirectory,\n        `${String(attachments.length + 1).padStart(3, '0')}-${sanitizePathSegment(rawBaseName)}${extension}`,\n      );\n\n      if (options.path) {\n        await copyFile(options.path, outputPath);\n      } else {\n        await writeFile(outputPath, options.body as Buffer | string);\n      }\n\n      attachments.push({\n        name,\n        contentType: options.contentType ?? 'application/octet-stream',\n        path: outputPath,\n      });\n    },\n  };\n\n  const testInfo = new Proxy(supported, {\n    get(target, property, receiver) {\n      if (typeof property === 'symbol' || property in target) {\n        return Reflect.get(target, property, receiver);\n      }\n\n      throw new Error(\n        `RUN_SPEC_UNSUPPORTED_TEST_INFO_CAPABILITY: testInfo.${String(property)} is not supported by browse-tool run_spec`,\n      );\n    },\n  });\n\n  return {\n    testInfo,\n    getAttachments: () => [...attachments],\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 {\n  getCollectedSuite,\n  initCollector,\n  resetCollector,\n  splitUseConfig,\n  type TestBlock,\n  type TestFixtures,\n  type TestSuite,\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';\nimport { createRunSpecTestInfo, type RunSpecAttachment } from './TestInfoAdapter.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  attachments?: RunSpecAttachment[];\n}\n\n/**\n * Result of executing a spec file with extension mode\n */\nexport interface ExtensionSpecExecutionResult {\n  specPath: string;\n  discoveredTests: number;\n  totalTests: number;\n  passed: number;\n  failed: number;\n  testResults: ExtensionTestResult[];\n  success: boolean;\n  outcome: 'completed' | 'failed' | 'incomplete';\n  incompleteReason?: 'no_tests_discovered' | 'no_tests_matched';\n  duration: number;\n  handoffOccurred: boolean;\n  sessionId?: string;\n  videoPath?: string;\n  warnings?: 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   * Page proxy to drive the spec with. Callers that serve concurrent agents pass\n   * their own per-call proxy; omitting it falls back to the shared instance.\n   */\n  page?: IExtensionPageProxy;\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\nexport class ExtensionSpecRunner implements IExtensionSpecRunner {\n  constructor(\n    private readonly bundler: ISpecBundlerService,\n    private readonly pageProxy: IExtensionPageProxy,\n    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    const proc = process as unknown as Record<string, unknown>;\n    const savedInitiator = proc.__pw_initiator__;\n\n    try {\n      initCollector(specPath);\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      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      proc.__pw_initiator__ = savedInitiator;\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    const pageProxy = options.page ?? this.pageProxy;\n\n    const startTime = Date.now();\n    const testResults: ExtensionTestResult[] = [];\n    let passed = 0;\n    let failed = 0;\n    let handoffOccurred = false;\n    const warnings = new Set<string>();\n    const serialFailures = new Set<number>();\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      if (testsToRun.length === 0) {\n        const incompleteReason = suite.allTests.length === 0 ? 'no_tests_discovered' : 'no_tests_matched';\n        return {\n          specPath,\n          discoveredTests: suite.allTests.length,\n          totalTests: 0,\n          passed: 0,\n          failed: 0,\n          testResults: [],\n          success: false,\n          outcome: 'incomplete',\n          incompleteReason,\n          duration: Date.now() - startTime,\n          handoffOccurred: false,\n          sessionId,\n        };\n      }\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 pageProxy.getSnapshot();\n        let url = pageProxy.url();\n        try {\n          url = await pageProxy.currentUrlAsync();\n        } catch {\n          // Fall back to the last cached URL if the page cannot be queried.\n        }\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: pageProxy,\n        requestHandoff,\n        specArgs,\n      };\n\n      for (const test of testsToRun) {\n        const { runtimeUse, unsupportedKeys, fixtureOverrides } = splitUseConfig(test.config.use);\n\n        if (unsupportedKeys.length > 0) {\n          warnings.add(`test.use() ignores unsupported fixture keys in extension mode: ${unsupportedKeys.join(', ')}`);\n        }\n\n        if (Object.keys(runtimeUse).length > 0) {\n          warnings.add(\n            `test.use() runtime browser config is not applied in extension mode: ${Object.keys(runtimeUse).join(', ')}`,\n          );\n        }\n\n        const serialScopeId = test.serialScopeId;\n        const shouldSkipDueToSerial = serialScopeId !== undefined && serialFailures.has(serialScopeId);\n\n        if (test.skip || shouldSkipDueToSerial) {\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        const testInfoAdapter = createRunSpecTestInfo(sessionId, test.fullTitle);\n\n        try {\n          await test.fn({ ...fixtures, ...fixtureOverrides } as unknown as TestFixtures, testInfoAdapter.testInfo);\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            attachments: testInfoAdapter.getAttachments(),\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            attachments: testInfoAdapter.getAttachments(),\n          });\n          failed++;\n          if (serialScopeId !== undefined) {\n            serialFailures.add(serialScopeId);\n          }\n        }\n      }\n\n      return {\n        specPath,\n        discoveredTests: suite.allTests.length,\n        totalTests: testsToRun.length,\n        passed,\n        failed,\n        testResults,\n        success: failed === 0,\n        outcome: failed === 0 ? 'completed' : 'failed',\n        duration: Date.now() - startTime,\n        handoffOccurred,\n        sessionId,\n        warnings: warnings.size > 0 ? [...warnings] : undefined,\n      };\n    } catch (error) {\n      return {\n        specPath,\n        discoveredTests: 0,\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        outcome: 'failed',\n        duration: Date.now() - startTime,\n        handoffOccurred: false,\n        sessionId,\n        warnings: warnings.size > 0 ? [...warnings] : undefined,\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 * 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 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\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 type { Browser, BrowserContext, Page } from 'playwright';\n\nimport { getCurrentOwnerId } from '../utils/ownerContext.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  /** Owner (MCP session) that created this page, for concurrent-agent isolation */\n  ownerId?: 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  /** Owner (MCP session) that created this page */\n  ownerId?: 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  findByOwner(ownerId: 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\nexport class PageRegistry implements IPageRegistry {\n  private pages: Map<string, PageEntry> = new Map();\n  private pageIdCounter = 0;\n\n  constructor(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, ownerId, 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      ownerId: ownerId ?? getCurrentOwnerId(),\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      ownerId: getCurrentOwnerId(),\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   * Finds all pages created by a specific owner (MCP session).\n   */\n  findByOwner(ownerId: string): PageEntry[] {\n    return this.list().filter((entry) => entry.ownerId === ownerId);\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   * The id counter deliberately keeps counting: ids that are never reused cannot\n   * inherit state left behind for a long-dead page of the same name, such as an\n   * extension task circuit breaker.\n   */\n  clear(): void {\n    this.pages.clear();\n  }\n}\n","import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport {\n  chmod,\n  lstat,\n  mkdir,\n  open,\n  readdir,\n  readFile,\n  realpath,\n  rename,\n  rm,\n  unlink,\n  writeFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path';\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  /** Default window mode when a launch does not say (cloak engine defaults to headed). */\n  headless?: boolean;\n  /** Cloak engine: derive timezone and locale from the proxy exit IP (needs mmdb-lib). */\n  geoip?: boolean;\n  createdAt: string;\n  updatedAt: string;\n}\n\nexport interface ProfileServiceOptions {\n  profilesDirectory?: string;\n  /** Bound runtimes must never silently discard corrupt authentication state. */\n  strictStorage?: boolean;\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  acquireLease?(name: string): Promise<() => Promise<void>>;\n  hasLease?(name: string): boolean;\n}\n\nconst PROFILE_LEASE = '.browse-runtime.lock';\n\nexport class ProfileService implements IProfileService {\n  private readonly profilesDir: string;\n  private readonly strictStorage: boolean;\n  private readonly leases = new Map<string, string>();\n\n  constructor(options: ProfileServiceOptions = {}) {\n    this.profilesDir = resolve(options.profilesDirectory ?? join(homedir(), '.browse-tool', 'profiles'));\n    this.strictStorage = options.strictStorage ?? false;\n  }\n\n  getProfilesDir(): string {\n    return this.profilesDir;\n  }\n\n  private getProfileDir(name: string): string {\n    if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) || name === '.' || name === '..') {\n      throw new Error('Invalid browser profile name');\n    }\n    return join(this.getProfilesDir(), name);\n  }\n\n  private async checkedDirectory(name: string): Promise<string> {\n    const directory = this.getProfileDir(name);\n    await mkdir(this.getProfilesDir(), { recursive: true, mode: 0o700 });\n    const root = await realpath(this.getProfilesDir());\n    if (this.strictStorage) await chmod(this.getProfilesDir(), 0o700);\n    if (existsSync(directory)) {\n      if ((await lstat(directory)).isSymbolicLink()) throw new Error('Unsafe browser profile directory');\n      if (this.strictStorage) await chmod(directory, 0o700);\n      const resolved = await realpath(directory);\n      const inside = relative(root, resolved);\n      if (isAbsolute(inside) || inside === '..' || inside.startsWith(`..${sep}`) || inside === '') {\n        throw new Error('Browser profile escapes its storage root');\n      }\n    }\n    return directory;\n  }\n\n  private async checkedFile(name: string, file: string): Promise<string> {\n    const directory = await this.checkedDirectory(name);\n    const target = join(directory, file);\n    try {\n      const stat = await lstat(target);\n      if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('Unsafe browser profile file');\n      if (this.strictStorage) await chmod(target, 0o600);\n    } catch (error) {\n      if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) throw error;\n    }\n    const lock = join(directory, PROFILE_LEASE);\n    if (existsSync(lock) && !this.leases.has(name)) throw new Error('Browser profile is busy');\n    return target;\n  }\n\n  hasLease(name: string): boolean {\n    return this.leases.has(name);\n  }\n\n  /**\n   * A filesystem lease is held for the whole browser lifetime. It records the\n   * holder's pid so a lease left by a crashed daemon can be reclaimed; a lease\n   * whose holder is alive (or unknown, for older leases) is never stolen.\n   */\n  async acquireLease(name: string): Promise<() => Promise<void>> {\n    const directory = await this.checkedDirectory(name);\n    await mkdir(directory, { recursive: true, mode: 0o700 });\n    const lock = join(directory, PROFILE_LEASE);\n    const token = JSON.stringify({ token: randomUUID(), pid: process.pid, startedAt: new Date().toISOString() });\n    const openLease = () => open(lock, 'wx', 0o600);\n    const handle = await openLease()\n      .catch(async () => {\n        // ponytail: two daemons reclaiming the same stale lease at once can both win\n        // (check-then-unlink race). Fine for one daemon per user; use an atomic\n        // rename-and-verify if several daemons share a profiles dir.\n        if (!(await isStaleLease(lock))) throw new Error('Browser profile is busy');\n        await unlink(lock).catch(() => undefined);\n        return openLease();\n      })\n      .catch(() => {\n        throw new Error('Browser profile is busy');\n      });\n    let identity: { dev: number; ino: number } | undefined;\n    try {\n      identity = await handle.stat();\n      await handle.writeFile(token);\n      await handle.close();\n    } catch {\n      await handle.close().catch(() => undefined);\n      const current = await lstat(lock).catch(() => undefined);\n      // Clean up only the inode this acquisition created, never a replacement or stale lease.\n      if (identity && current?.dev === identity.dev && current.ino === identity.ino) {\n        await unlink(lock).catch(() => undefined);\n      }\n      throw new Error('Browser profile lease initialization failed');\n    }\n    this.leases.set(name, token);\n    let released = false;\n    return async () => {\n      if (released) return;\n      if ((await readFile(lock, 'utf8')) !== token) throw new Error('Browser profile lease ownership changed');\n      await unlink(lock);\n      this.leases.delete(name);\n      released = true;\n    };\n  }\n\n  async list(): Promise<BrowserProfile[]> {\n    await mkdir(this.getProfilesDir(), { recursive: true, mode: 0o700 });\n    const entries = await readdir(this.getProfilesDir(), { withFileTypes: true });\n    const profiles: BrowserProfile[] = [];\n    for (const entry of entries) {\n      if (entry.isDirectory()) {\n        const profile = await this.get(entry.name);\n        if (profile) profiles.push(profile);\n      }\n    }\n    return profiles;\n  }\n\n  async get(name: string): Promise<BrowserProfile | null> {\n    const file = await this.checkedFile(name, 'profile.json');\n    if (!existsSync(file)) return null;\n    return JSON.parse(await readFile(file, 'utf8')) as BrowserProfile;\n  }\n\n  async create(profile: Omit<BrowserProfile, 'createdAt' | 'updatedAt'>): Promise<BrowserProfile> {\n    if (await this.get(profile.name)) throw new Error(`Profile \"${profile.name}\" already exists`);\n    await mkdir(await this.checkedDirectory(profile.name), { recursive: true, mode: 0o700 });\n    const now = new Date().toISOString();\n    const fullProfile = { ...profile, createdAt: now, updatedAt: now };\n    await writeFile(await this.checkedFile(profile.name, 'profile.json'), JSON.stringify(fullProfile, null, 2), {\n      mode: 0o600,\n    });\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) throw new Error(`Profile \"${name}\" not found`);\n    const updated = {\n      ...existing,\n      ...updates,\n      name: existing.name,\n      createdAt: existing.createdAt,\n      updatedAt: new Date().toISOString(),\n    };\n    await writeFile(await this.checkedFile(name, 'profile.json'), JSON.stringify(updated, null, 2), { mode: 0o600 });\n    return updated;\n  }\n\n  async delete(name: string): Promise<void> {\n    if (!(await this.get(name))) throw new Error(`Profile \"${name}\" not found`);\n    if (this.leases.has(name)) throw new Error('Cannot delete an active browser profile');\n    await rm(await this.checkedDirectory(name), { recursive: true });\n  }\n\n  async saveStorageState(name: string, state: Record<string, unknown>): Promise<void> {\n    if (!(await this.get(name))) throw new Error(`Profile \"${name}\" not found`);\n    const target = await this.checkedFile(name, 'storage-state.json');\n    const temporary = `${target}.${randomUUID()}.tmp`;\n    const handle = await open(temporary, 'wx', 0o600);\n    try {\n      await handle.writeFile(JSON.stringify(state, null, 2));\n      await handle.sync();\n    } catch (error) {\n      await handle.close();\n      await unlink(temporary);\n      throw error;\n    }\n    await handle.close();\n    try {\n      await rename(temporary, target);\n    } catch (error) {\n      await unlink(temporary);\n      throw error;\n    }\n  }\n\n  async loadStorageState(name: string): Promise<Record<string, unknown> | null> {\n    const file = await this.checkedFile(name, 'storage-state.json');\n    if (!existsSync(file)) return null;\n    const content = await readFile(file, 'utf8');\n    try {\n      return JSON.parse(content) as Record<string, unknown>;\n    } catch {\n      if (this.strictStorage) throw new Error('Browser profile state is invalid');\n      // Standalone compatibility: quarantine corrupt state, never print its contents.\n      await rename(file, `${file}.corrupt-${Date.now()}`);\n      console.warn('[ProfileService] Corrupt browser profile state was quarantined');\n      return null;\n    }\n  }\n}\n\n/** True when the lease names a holder pid that no longer runs. */\nasync function isStaleLease(lock: string): Promise<boolean> {\n  let pid: unknown;\n  try {\n    pid = (JSON.parse(await readFile(lock, 'utf8')) as { pid?: unknown }).pid;\n  } catch {\n    return false;\n  }\n  if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n  try {\n    process.kill(pid, 0);\n    return false;\n  } catch (error) {\n    // EPERM means the process exists under another user: still alive.\n    return error instanceof Error && 'code' in error && error.code === 'ESRCH';\n  }\n}\n","/**\n * SetupRunner\n *\n * DESIGN PATTERNS:\n * - Service pattern for executing setup files before specs\n * - Constructor injection; createBrowseRuntime wires the instances\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 type { Browser, BrowserContext, Page } from 'playwright';\n\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\nexport class SetupRunner implements ISetupRunner {\n  constructor(private readonly bundler: ISpecBundlerService) {}\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 { randomUUID } from 'node:crypto';\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';\n\nimport { build as esbuildBuild } from 'esbuild';\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 */\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    // Unique per call so concurrent/interleaved hooks bundles never collide on a\n    // shared output file (a sibling cleanup() could otherwise unlink it mid-import).\n    const outputPath = join(outdir, `${outputName}-${randomUUID()}.js`);\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        outfile: outputPath,\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      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 { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { basename, dirname, join, relative, resolve } from 'node:path';\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\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 * - Constructor injection; createBrowseRuntime wires the instances\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 { readFile } from 'node:fs/promises';\n\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\nexport class SpecMetadataService implements ISpecMetadataService {\n  constructor(private readonly bundler: ISpecBundlerService) {}\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 * - Constructor injection; createBrowseRuntime wires the instances\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 type { Browser, BrowserContext, BrowserContextOptions, Page } from 'playwright';\nimport playwright from 'playwright';\n\nimport {\n  getCollectedSuite,\n  initCollector,\n  resetCollector,\n  SkipTestError,\n  splitUseConfig,\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';\nimport { createRunSpecTestInfo, type RunSpecAttachment } from './TestInfoAdapter.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  /** Evidence attached through testInfo.attach(). */\n  attachments?: RunSpecAttachment[];\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  /** Non-fatal compatibility warnings collected while running the spec. */\n  warnings?: string[];\n  /** Browser instance kept open when keepBrowserOpen is true. */\n  browser?: Browser;\n  /** Pages kept open when keepBrowserOpen is true — one per test that ran. */\n  keptPages?: { page: Page; context: BrowserContext }[];\n}\n\nfunction createContextOptions(\n  baseURL: string | undefined,\n  useConfig: Record<string, unknown> | undefined,\n): {\n  options: BrowserContextOptions;\n  warnings: string[];\n} {\n  const { runtimeUse, unsupportedKeys } = splitUseConfig(useConfig);\n  const options: BrowserContextOptions = {\n    ...(baseURL ? { baseURL } : {}),\n    ...(runtimeUse as BrowserContextOptions),\n  };\n\n  return {\n    options,\n    warnings:\n      unsupportedKeys.length > 0 ? [`test.use() ignores unsupported fixture keys: ${unsupportedKeys.join(', ')}`] : [],\n  };\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 browser type to launch */\n  browserType?: 'chromium' | 'firefox' | 'webkit';\n  /** Run browser in headless mode */\n  headless?: boolean;\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  /** When true, keep the browser and test contexts open after spec execution */\n  keepBrowserOpen?: boolean;\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\nexport class SpecRunner implements ISpecRunner {\n  constructor(private readonly bundler: ISpecBundlerService) {}\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    const proc = process as unknown as Record<string, unknown>;\n    const savedInitiator = proc.__pw_initiator__;\n\n    try {\n      initCollector(specPath);\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      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      proc.__pw_initiator__ = savedInitiator;\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, browserType = 'chromium', headless = true, hooksPath, baseURL, keepBrowserOpen } = options;\n\n    const startTime = Date.now();\n    const testResults: TestResult[] = [];\n    let passed = 0;\n    let failed = 0;\n    const warnings = new Set<string>();\n    const serialFailures = new Set<number>();\n    const keptPages: { page: Page; context: BrowserContext }[] = [];\n\n    const browser = await playwright[browserType].launch({ channel: 'chrome', headless });\n\n    try {\n      const suite = await this.loadSpec(specPath, hooksPath);\n\n      for (const test of suite.allTests) {\n        const { options: contextOptions, warnings: configWarnings } = createContextOptions(baseURL, test.config.use);\n        for (const warning of configWarnings) {\n          warnings.add(warning);\n        }\n\n        const serialScopeId = test.serialScopeId;\n        const shouldSkipDueToSerial = serialScopeId !== undefined && serialFailures.has(serialScopeId);\n\n        if (test.skip || shouldSkipDueToSerial) {\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 testContext = await browser.newContext(contextOptions);\n        const testPage = await testContext.newPage();\n        const fixtures: TestFixtures = { page: testPage, context: testContext, browser, playwright };\n        const testStartTime = Date.now();\n        const testInfoAdapter = createRunSpecTestInfo(options.sessionId, test.fullTitle);\n\n        try {\n          await test.fn(fixtures, testInfoAdapter.testInfo);\n\n          testResults.push({\n            title: test.title,\n            fullTitle: test.fullTitle,\n            passed: true,\n            error: null,\n            duration: Date.now() - testStartTime,\n            attachments: testInfoAdapter.getAttachments(),\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              attachments: testInfoAdapter.getAttachments(),\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              attachments: testInfoAdapter.getAttachments(),\n            });\n            failed++;\n            if (serialScopeId !== undefined) {\n              serialFailures.add(serialScopeId);\n            }\n          }\n        } finally {\n          if (!keepBrowserOpen) {\n            await testContext.close().catch(() => {\n              /* context may already be closed */\n            });\n          } else {\n            keptPages.push({ page: testPage, context: testContext });\n          }\n        }\n      }\n\n      if (!keepBrowserOpen) {\n        for (const ctx of browser.contexts()) {\n          await ctx.close().catch(() => {});\n        }\n        await browser.close();\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        warnings: warnings.size > 0 ? [...warnings] : undefined,\n        browser: keepBrowserOpen ? browser : undefined,\n        keptPages: keptPages.length > 0 ? keptPages : undefined,\n      };\n    } catch (error) {\n      for (const ctx of browser.contexts()) {\n        await ctx.close().catch(() => {});\n      }\n      await browser.close().catch(() => {});\n\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        warnings: warnings.size > 0 ? [...warnings] : undefined,\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 {\n      specPath,\n      browserType = 'chromium',\n      headless = true,\n      testFilter,\n      specArgs,\n      hooksPath,\n      baseURL,\n      keepBrowserOpen,\n    } = options;\n\n    const startTime = Date.now();\n    const testResults: TestResult[] = [];\n    let passed = 0;\n    let failed = 0;\n    const warnings = new Set<string>();\n    const serialFailures = new Set<number>();\n    const keptPages: { page: Page; context: BrowserContext }[] = [];\n\n    const browser = await playwright[browserType].launch({ channel: 'chrome', headless });\n\n    try {\n      const suite = await this.loadSpec(specPath, hooksPath);\n\n      const testsToRun = testFilter ? this.filterTests(suite.allTests, testFilter) : suite.allTests;\n\n      for (const test of testsToRun) {\n        const { options: contextOptions, warnings: configWarnings } = createContextOptions(baseURL, test.config.use);\n        for (const warning of configWarnings) {\n          warnings.add(warning);\n        }\n\n        const serialScopeId = test.serialScopeId;\n        const shouldSkipDueToSerial = serialScopeId !== undefined && serialFailures.has(serialScopeId);\n\n        if (test.skip || shouldSkipDueToSerial) {\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 testContext = await browser.newContext(contextOptions);\n        const testPage = await testContext.newPage();\n        const fixtures: TestFixturesEnhanced = { page: testPage, context: testContext, browser, playwright, specArgs };\n        const testStartTime = Date.now();\n        const testInfoAdapter = createRunSpecTestInfo(options.sessionId, test.fullTitle);\n\n        try {\n          await test.fn(fixtures, testInfoAdapter.testInfo);\n\n          testResults.push({\n            title: test.title,\n            fullTitle: test.fullTitle,\n            passed: true,\n            error: null,\n            duration: Date.now() - testStartTime,\n            attachments: testInfoAdapter.getAttachments(),\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              attachments: testInfoAdapter.getAttachments(),\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              attachments: testInfoAdapter.getAttachments(),\n            });\n            failed++;\n            if (serialScopeId !== undefined) {\n              serialFailures.add(serialScopeId);\n            }\n          }\n        } finally {\n          if (!keepBrowserOpen) {\n            await testContext.close().catch(() => {\n              /* context may already be closed */\n            });\n          } else {\n            keptPages.push({ page: testPage, context: testContext });\n          }\n        }\n      }\n\n      if (!keepBrowserOpen) {\n        for (const ctx of browser.contexts()) {\n          await ctx.close().catch(() => {});\n        }\n        await browser.close();\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        warnings: warnings.size > 0 ? [...warnings] : undefined,\n        browser: keepBrowserOpen ? browser : undefined,\n        keptPages: keptPages.length > 0 ? keptPages : undefined,\n      };\n    } catch (error) {\n      for (const ctx of browser.contexts()) {\n        await ctx.close().catch(() => {});\n      }\n      await browser.close().catch(() => {});\n\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        warnings: warnings.size > 0 ? [...warnings] : undefined,\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 * ToolExecutor\n *\n * DESIGN PATTERNS:\n * - Service pattern for business logic encapsulation\n * - Strategy pattern for mode-aware execution\n * - Constructor injection; createBrowseRuntime wires the instances\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\nimport { isOwnedByOtherAgent } from '../utils/ownerContext.js';\nimport type { IBrowserService } from './BrowserService.js';\nimport type { ExtensionToolDelegator } from './ExtensionToolDelegator.js';\nimport type { IPageRegistry } from './PageRegistry.js';\nimport { type ITelemetryService, TelemetryService } 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 */\nexport class ToolExecutor {\n  constructor(\n    private pageRegistry: IPageRegistry,\n    private browserService: IBrowserService,\n    private delegator: ExtensionToolDelegator,\n    private telemetry: ITelemetryService = new TelemetryService(),\n  ) {}\n\n  private formatPageNotFoundMessage(pageId: string): string {\n    // Only pages the caller may use: listing another session's pages would leak their URLs.\n    const pages = (this.pageRegistry.list() ?? []).filter((page) => !isOwnedByOtherAgent(page));\n    if (pages.length === 0) {\n      return `Page \"${pageId}\" not found. No pages are currently registered. Launch or select a browser page, then retry with the returned pageId.`;\n    }\n\n    const pageHints = pages\n      .slice(0, 5)\n      .map((page) => `${page.id} (${page.url || 'about:blank'}, browser ${page.browserId})`)\n      .join('; ');\n    const suffix = pages.length > 5 ? `; and ${pages.length - 5} more` : '';\n\n    return `Page \"${pageId}\" not found. Available pages: ${pageHints}${suffix}. Use browser_list_pages to refresh page IDs, then retry with a current pageId.`;\n  }\n\n  private formatBrowserNotFoundMessage(browserId: string): string {\n    const browsers = this.browserService.listBrowsers() ?? [];\n    if (browsers.length === 0) {\n      return `Browser \"${browserId}\" not found. No browsers are currently registered. Launch a browser, then retry with the returned browserId.`;\n    }\n\n    const browserHints = browsers\n      .slice(0, 5)\n      .map((browser) => `${browser.id} (${browser.mode}, pages ${browser.pageIds.size})`)\n      .join('; ');\n    const suffix = browsers.length > 5 ? `; and ${browsers.length - 5} more` : '';\n\n    return `Browser \"${browserId}\" not found. Available browsers: ${browserHints}${suffix}. Use browser_list_pages to refresh browser IDs, then retry with a current browserId.`;\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          const message = this.formatPageNotFoundMessage(pageId);\n          span?.setStatus({ code: SpanStatusCode.ERROR, message });\n          return {\n            content: [{ type: 'text', text: message }],\n            isError: true,\n          };\n        }\n\n        // Both modes pass through here; the extension path never reaches a tool's own resolvePage check.\n        // Another session's page answers exactly like a missing one, so its existence does not leak.\n        if (isOwnedByOtherAgent(pageEntry)) {\n          const message = this.formatPageNotFoundMessage(pageId);\n          span?.setStatus({ code: SpanStatusCode.ERROR, message });\n          return { content: [{ type: 'text', text: message }], isError: true };\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          const message = this.formatBrowserNotFoundMessage(browserId);\n          span?.setStatus({ code: SpanStatusCode.ERROR, message });\n          return {\n            content: [{ type: 'text', text: message }],\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 { type ChildProcess, spawn } from 'node:child_process';\nimport { resolve } from 'node:path';\n\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\nexport class WebServerManager implements IWebServerManager {\n  private serverProcess: ChildProcess | null = null;\n  private currentUrl: string | null = null;\n  private cleanupRegistered = false;\n\n  /**\n   * Stops the dev server when the process exits or is signalled. Only a process\n   * that owns its lifetime (the daemon) calls this; a library host must not get\n   * listeners on `process`. Registers once.\n   */\n  registerProcessCleanup(): 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\nexport const TaskCancelSchema = z.object({\n  type: z.literal('task:cancel'),\n  payload: z.object({\n    taskId: z.string(),\n    reason: z.string(),\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  TaskCancelSchema,\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 TaskCancel = z.infer<typeof TaskCancelSchema>;\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 type { WSContext } from 'hono/ws';\n\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   * Monotonic registration order. Timestamps are too coarse to tell apart a\n   * reconnect's old and new sockets, which can be created in the same\n   * millisecond, so ordering uses this instead.\n   */\n  sequence: number;\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/** WebSocket readyState for a socket that can still carry messages. */\nconst WS_READY_STATE_OPEN = 1;\n\n/** How often to look for connections that died without a close frame. */\nconst LIVENESS_SWEEP_INTERVAL_MS = 30_000;\n\n/**\n * Silence that means a connection is gone. The extension pings every 30s, so\n * this is three missed pings.\n */\nconst CONNECTION_STALE_AFTER_MS = 90_000;\n\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  private livenessSweepTimer?: NodeJS.Timeout;\n  private connectionSequence = 0;\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      sequence: ++this.connectionSequence,\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    // Remove from the maps before notifying, so a handler asking whether the\n    // browser still has a connection gets the post-removal answer.\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    this.eventHandlers.onDisconnect?.(connection);\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    // Sending on a closed socket is a silent no-op, so reporting success from\n    // `send` alone would strand the message. readyState is the only real signal.\n    if (!WebSocketHub.isLive(connection)) {\n      return false;\n    }\n\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  /** Whether this connection's socket is still open. */\n  private static isLive(connection: ExtensionConnection): boolean {\n    return connection.ws.readyState === WS_READY_STATE_OPEN;\n  }\n\n  /**\n   * Deliver a message to the browser's most recently established live socket,\n   * dropping any dead ones found along the way.\n   *\n   * Newest-first matters after a reconnect: the old socket may not have emitted\n   * its close yet, and sending to it would silently lose the message.\n   */\n  private sendToBrowser(browserId: string, message: ServerMessage): boolean {\n    const candidates = this.getConnectionsByBrowser(browserId).sort((a, b) => b.sequence - a.sequence);\n\n    for (const connection of candidates) {\n      if (!WebSocketHub.isLive(connection)) {\n        this.removeConnection(connection.id);\n        continue;\n      }\n\n      if (this.sendToConnection(connection, message)) {\n        return true;\n      }\n    }\n\n    return false;\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 delivered = this.sendToBrowser(browserId, task);\n    if (!delivered) {\n      // Reporting this honestly is what lets the caller fall back to the HTTP\n      // polling queue instead of waiting out the task timeout.\n      console.warn(`[WebSocketHub] No live connection for browser ${browserId}`);\n    }\n    return delivered;\n  }\n\n  cancelTask(browserId: string, taskId: string, reason: string): boolean {\n    return this.sendToBrowser(browserId, { type: 'task:cancel', payload: { taskId, reason } });\n  }\n\n  /**\n   * Start dropping connections that stopped responding.\n   *\n   * A laptop suspend or a crashed extension leaves a socket that never emits a\n   * close frame; without this sweep the hub keeps believing it can reach it.\n   */\n  startLivenessSweep(\n    intervalMs: number = LIVENESS_SWEEP_INTERVAL_MS,\n    staleAfterMs: number = CONNECTION_STALE_AFTER_MS,\n  ): void {\n    if (this.livenessSweepTimer) {\n      return;\n    }\n\n    this.livenessSweepTimer = setInterval(() => {\n      this.sweepDeadConnections(staleAfterMs);\n    }, intervalMs);\n    this.livenessSweepTimer.unref?.();\n  }\n\n  stopLivenessSweep(): void {\n    if (!this.livenessSweepTimer) {\n      return;\n    }\n\n    clearInterval(this.livenessSweepTimer);\n    this.livenessSweepTimer = undefined;\n  }\n\n  private sweepDeadConnections(staleAfterMs: number): void {\n    const now = Date.now();\n\n    for (const connection of Array.from(this.connections.values())) {\n      const isStale = now - connection.lastMessageAt.getTime() > staleAfterMs;\n      if (WebSocketHub.isLive(connection) && !isStale) {\n        continue;\n      }\n\n      console.warn(\n        `[WebSocketHub] Dropping unresponsive connection ${connection.id} for browser ${connection.browserId}`,\n      );\n      try {\n        connection.ws.close();\n      } catch {\n        // The socket is already gone; removing our bookkeeping is what matters.\n      }\n      this.removeConnection(connection.id);\n    }\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 * - Constructor injection; createBrowseRuntime wires the instances\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport type { Page } from 'playwright';\nimport type { z } from 'zod';\n\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';\nimport { assertPageOwnership } from '../utils/ownerContext.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 */\nexport abstract class BaseTool<TInput = unknown> implements Tool<TInput> {\n  constructor(\n    protected readonly pageRegistry: IPageRegistry,\n    protected readonly browserService: IBrowserService,\n    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   * Returns the Zod schema for this tool's input.\n   * Used for schema-guided coercion and validation.\n   */\n  abstract getInputSchema(): z.ZodObject<z.ZodRawShape>;\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    const entry = this.pageRegistry.get(pageId);\n    if (entry) {\n      assertPageOwnership(entry, this.constructor.name);\n    }\n    return entry;\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    assertPageOwnership(entry, this.constructor.name);\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    assertPageOwnership(entry, this.constructor.name);\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","/// <reference lib=\"dom\" />\n\n/**\n * ResolveLocatorTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Returns ranked candidates for selector recovery workflows\n * - Supports both Playwright and extension execution paths\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport type { Frame, Page } from 'playwright';\nimport { z } from 'zod';\n\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport { type ElementSelector, ElementSelectorSchema } 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 ResolveLocatorToolInput extends ElementSelector, PageTargetInput {\n  limit?: number;\n}\n\nexport const ResolveLocatorToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  limit: z.number().int().min(1).max(10).optional().default(5).describe('Maximum number of candidates to return'),\n});\n\nexport async function resolveLocatorCandidates(\n  page: Page,\n  selector: ElementSelector,\n  limit = 5,\n): Promise<{\n  totalCandidates: number;\n  candidates: Array<{\n    score: number;\n    reasons: string[];\n    candidate: {\n      tagName: string;\n      id: string;\n      className: string;\n      role: string;\n      accessibleName: string;\n      label: string | null;\n      placeholder: string | null;\n      testId: string | null;\n      textContent: string;\n      visible: boolean;\n      enabled: boolean;\n      rect: { x: number; y: number; width: number; height: number };\n    };\n  }>;\n}> {\n  const context = await resolveContext(page, selector.frame);\n  return context.evaluate(\n    ({ selector: selectorSpec, limit: candidateLimit }) => {\n      const normalizeText = (value: string | null | undefined): string => (value ?? '').replace(/\\s+/g, ' ').trim();\n      const matchesText = (actual: string | null | undefined, expected: string | undefined, exact = false): boolean => {\n        if (!expected) {\n          return false;\n        }\n        const normalizedActual = normalizeText(actual);\n        const normalizedExpected = normalizeText(expected);\n        if (!normalizedActual || !normalizedExpected) {\n          return false;\n        }\n        return exact\n          ? normalizedActual === normalizedExpected\n          : normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());\n      };\n      const getLabelText = (element: Element): string | null => {\n        if (\n          element instanceof HTMLInputElement ||\n          element instanceof HTMLTextAreaElement ||\n          element instanceof HTMLSelectElement\n        ) {\n          if (typeof element.labels?.length === 'number' && element.labels.length > 0) {\n            return normalizeText(element.labels[0]?.textContent);\n          }\n          if (element.id) {\n            const explicitLabel = document.querySelector(`label[for=\"${CSS.escape(element.id)}\"]`);\n            if (explicitLabel?.textContent) {\n              return normalizeText(explicitLabel.textContent);\n            }\n          }\n        }\n        const wrappedLabel = element.closest('label');\n        return wrappedLabel?.textContent ? normalizeText(wrappedLabel.textContent) : null;\n      };\n      const getAccessibleName = (element: Element): string => {\n        const ariaLabel = element.getAttribute('aria-label');\n        if (ariaLabel) {\n          return normalizeText(ariaLabel);\n        }\n        const labelledBy = element.getAttribute('aria-labelledby');\n        if (labelledBy) {\n          const labels = labelledBy\n            .split(/\\s+/)\n            .map((id) => normalizeText(document.getElementById(id)?.textContent))\n            .filter(Boolean);\n          if (labels.length > 0) {\n            return labels.join(' ');\n          }\n        }\n        const label = getLabelText(element);\n        if (label) {\n          return label;\n        }\n        if (element instanceof HTMLInputElement && element.placeholder) {\n          return normalizeText(element.placeholder);\n        }\n        return normalizeText(element.textContent);\n      };\n      const getRole = (element: Element): string => {\n        const ariaRole = element.getAttribute('role');\n        if (ariaRole) {\n          return ariaRole;\n        }\n        if (element instanceof HTMLInputElement) {\n          const type = (element.type || 'text').toLowerCase();\n          const inputRoles: Record<string, string> = {\n            button: 'button',\n            checkbox: 'checkbox',\n            email: 'textbox',\n            number: 'spinbutton',\n            password: 'textbox',\n            radio: 'radio',\n            range: 'slider',\n            search: 'searchbox',\n            submit: 'button',\n            tel: 'textbox',\n            text: 'textbox',\n            url: 'textbox',\n          };\n          return inputRoles[type] || 'textbox';\n        }\n        const tagName = element.tagName.toLowerCase();\n        const roleMap: Record<string, string> = {\n          a: element.hasAttribute('href') ? 'link' : 'none',\n          button: 'button',\n          img: 'img',\n          option: 'option',\n          select: 'combobox',\n          textarea: 'textbox',\n        };\n        return roleMap[tagName] || 'none';\n      };\n      const SKIP_TAGS = new Set(['html', 'body', 'head', 'script', 'style', 'noscript', 'template', 'meta', 'link']);\n      const scoreCandidate = (element: Element) => {\n        if (SKIP_TAGS.has(element.tagName.toLowerCase())) return null;\n        const exact = selectorSpec.exact ?? false;\n        const accessibleName = getAccessibleName(element);\n        const label = getLabelText(element);\n        const placeholder = element.getAttribute('placeholder');\n        const textContent = normalizeText(element.textContent);\n        const role = getRole(element);\n        const testId = element.getAttribute('data-testid') || element.getAttribute('data-test-id');\n        const rect = element.getBoundingClientRect();\n        const visible = rect.width > 0 && rect.height > 0;\n        const enabled =\n          !(\n            element instanceof HTMLButtonElement ||\n            element instanceof HTMLInputElement ||\n            element instanceof HTMLSelectElement ||\n            element instanceof HTMLTextAreaElement\n          ) || !element.disabled;\n        let score = 0;\n        let semanticSignals = 0;\n        const reasons: string[] = [];\n        const isInteractive =\n          role !== 'none' ||\n          element instanceof HTMLButtonElement ||\n          element instanceof HTMLInputElement ||\n          element instanceof HTMLSelectElement ||\n          element instanceof HTMLTextAreaElement ||\n          (element instanceof HTMLAnchorElement && element.hasAttribute('href'));\n\n        if (selectorSpec.role && role === selectorSpec.role) {\n          score += 30;\n          semanticSignals += 1;\n          reasons.push('role match');\n        }\n        if (selectorSpec.name) {\n          if (matchesText(accessibleName, selectorSpec.name, true)) {\n            score += 60;\n            semanticSignals += 1;\n            reasons.push('exact accessible name match');\n          } else if (matchesText(accessibleName, selectorSpec.name, exact)) {\n            score += 35;\n            semanticSignals += 1;\n            reasons.push('accessible name match');\n          }\n        }\n        if (selectorSpec.label) {\n          if (matchesText(label, selectorSpec.label, true)) {\n            score += 55;\n            semanticSignals += 1;\n            reasons.push('exact label match');\n          } else if (matchesText(label, selectorSpec.label, exact)) {\n            score += 30;\n            semanticSignals += 1;\n            reasons.push('label match');\n          }\n        }\n        if (selectorSpec.placeholder && matchesText(placeholder, selectorSpec.placeholder, exact)) {\n          score += 25;\n          semanticSignals += 1;\n          reasons.push('placeholder match');\n        }\n        if (selectorSpec.text && matchesText(textContent, selectorSpec.text, exact)) {\n          score += 20;\n          semanticSignals += 1;\n          reasons.push('text match');\n        }\n        if (selectorSpec.testId && testId === selectorSpec.testId) {\n          score += 50;\n          semanticSignals += 1;\n          reasons.push('test id match');\n        }\n        if (isInteractive) {\n          score += 8;\n          reasons.push('interactive element');\n        }\n        if (visible) {\n          score += 10;\n          reasons.push('visible');\n        }\n        if (enabled) {\n          score += 5;\n          reasons.push('enabled');\n        }\n        if (semanticSignals === 0) {\n          return null;\n        }\n\n        return {\n          score,\n          reasons,\n          candidate: {\n            tagName: element.tagName.toLowerCase(),\n            id: element.id || '',\n            className: element.getAttribute('class') || '',\n            role,\n            accessibleName,\n            label,\n            placeholder,\n            testId,\n            textContent: textContent.slice(0, 500),\n            visible,\n            enabled,\n            rect: {\n              x: rect.x,\n              y: rect.y,\n              width: rect.width,\n              height: rect.height,\n            },\n          },\n        };\n      };\n\n      const allCandidates = Array.from(document.querySelectorAll('*'))\n        .map((element) => scoreCandidate(element))\n        .filter((entry): entry is NonNullable<typeof entry> => entry !== null && entry.score > 0)\n        .sort((left, right) => right.score - left.score)\n        .slice(0, candidateLimit);\n\n      return {\n        totalCandidates: allCandidates.length,\n        candidates: allCandidates,\n      };\n    },\n    { selector, limit },\n  );\n}\n\nexport class ResolveLocatorTool extends BaseTool<ResolveLocatorToolInput> {\n  static readonly TOOL_NAME = 'browser_resolve_locator';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ResolveLocatorToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: ResolveLocatorTool.TOOL_NAME,\n      description:\n        'Returns ranked locator candidates for a selector intent. Use this after a failed click/fill to recover using semantic matches instead of brittle guesses.',\n      inputSchema: z.toJSONSchema(ResolveLocatorToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: ResolveLocatorToolInput): Promise<CallToolResult> {\n    return this.safeExecute(async () =>\n      this.executeWithMode(\n        ResolveLocatorTool.TOOL_NAME,\n        input.pageId,\n        input as unknown as Record<string, unknown>,\n        async () => {\n          const page = this.resolvePage(input.pageId);\n          const selector = this.buildSelector(input);\n          const result = await resolveLocatorCandidates(page, selector, input.limit ?? 5);\n\n          return this.successJson({\n            pageId: input.pageId,\n            selector,\n            ...result,\n          });\n        },\n      ),\n    );\n  }\n\n  private buildSelector(input: ResolveLocatorToolInput): ElementSelector {\n    return {\n      uid: input.uid,\n      role: input.role,\n      name: input.name,\n      label: input.label,\n      placeholder: input.placeholder,\n      testId: input.testId,\n      text: input.text,\n      exact: input.exact,\n      selector: input.selector,\n      xpath: input.xpath,\n      frame: input.frame,\n    };\n  }\n}\n\nasync function resolveContext(page: Page, frameSelector?: string): Promise<Page | Frame> {\n  if (!frameSelector) {\n    return page;\n  }\n\n  const frameHandle = await page.locator(frameSelector).first().elementHandle();\n  const frame = await frameHandle?.contentFrame();\n  if (!frame) {\n    throw new Error(`Frame not found: ${frameSelector}`);\n  }\n  return frame;\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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';\nimport { resolveLocatorCandidates } from './ResolveLocatorTool.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\nexport const ClickToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  clickCount: z.number().optional().default(1).describe('Number of clicks (default: 1, use 2 for double-click)'),\n  button: z.enum(['left', 'middle', 'right']).optional().default('left').describe('Mouse button to use'),\n  modifiers: z\n    .array(z.enum(['Alt', 'Control', 'Meta', 'Shift']))\n    .optional()\n    .describe('Modifier keys to hold during click'),\n  delay: z.number().optional().describe('Time to wait between mousedown and mouseup in ms'),\n  position: z.object({ x: z.number(), y: z.number() }).optional().describe('Position offset within the element'),\n  timeout: z\n    .number()\n    .optional()\n    .default(DEFAULT_ACTION_TIMEOUT_MS)\n    .describe(`Timeout in milliseconds (default: ${DEFAULT_ACTION_TIMEOUT_MS})`),\n});\n\nexport class ClickTool extends BaseTool<ClickToolInput> {\n  static readonly TOOL_NAME = 'browser_click';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ClickToolInputSchema;\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: z.toJSONSchema(ClickToolInputSchema, { reused: 'inline' }),\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        const selector = this.buildSelector(input);\n\n        try {\n          const locator = await this.elementLocator.locate(page, selector);\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        } catch (error) {\n          const message = error instanceof Error ? error.message : String(error);\n          if (!message.includes('Element not found')) {\n            throw error;\n          }\n\n          const candidates = await resolveLocatorCandidates(page, selector, 5);\n          return this.error(\n            JSON.stringify(\n              {\n                error: message,\n                pageId: input.pageId,\n                selector,\n                ...candidates,\n              },\n              null,\n              2,\n            ),\n          );\n        }\n      },\n    );\n  }\n\n  private buildSelector(input: ClickToolInput): ElementSelector {\n    return {\n      uid: input.uid,\n      role: input.role,\n      name: input.name,\n      label: input.label,\n      placeholder: input.placeholder,\n      testId: input.testId,\n      text: input.text,\n      exact: input.exact,\n      selector: input.selector,\n      xpath: input.xpath,\n      frame: input.frame,\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const CloseBrowserToolInputSchema = z.object({\n  browserId: z.string().describe('The browser ID to close (returned by browser_launch)'),\n});\n\nexport class CloseBrowserTool extends BaseTool<CloseBrowserToolInput> {\n  static readonly TOOL_NAME = 'browser_close';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return CloseBrowserToolInputSchema;\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: z.toJSONSchema(CloseBrowserToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { assertPageOwnership } from '../utils/ownerContext.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface ClosePageToolInput {\n  /** Page ID to close */\n  pageId: string;\n}\n\nexport const ClosePageToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to close'),\n});\n\nexport class ClosePageTool extends BaseTool<ClosePageToolInput> {\n  static readonly TOOL_NAME = 'browser_close_page';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ClosePageToolInputSchema;\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: z.toJSONSchema(ClosePageToolInputSchema, { reused: 'inline' }),\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      assertPageOwnership(pageEntry, ClosePageTool.TOOL_NAME);\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        // Extension pages emit no Playwright close event, so this tool is the\n        // only trigger for the last-page policy.\n        const outcome = isLastPage\n          ? await this.browserService.handleLastPageClosed(browserId)\n          : { profileStateSaved: false, browserClosed: false };\n\n        return this.successJson({\n          closedPageId: input.pageId,\n          browserId,\n          wasCurrentPage,\n          newCurrentPageId: browser.currentPageId,\n          browserKeptOpen: !outcome.browserClosed,\n          message: outcome.browserClosed\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      // Playwright emits 'close' before close() resolves, so BrowserService has\n      // already started handling the last page. Joining that run reports what\n      // actually happened instead of guessing, and never saves state twice.\n      const outcome = isLastPage\n        ? await this.browserService.handleLastPageClosed(browserId)\n        : { profileStateSaved: false, browserClosed: false };\n\n      return this.successJson({\n        closedPageId: input.pageId,\n        browserId,\n        wasCurrentPage,\n        newCurrentPageId,\n        browserKeptOpen: !outcome.browserClosed,\n        profileStateSaved: outcome.profileStateSaved,\n        message:\n          isLastPage && !outcome.browserClosed && profileName\n            ? `Page \"${input.pageId}\" closed. Browser kept open for profile \"${profileName}\" reuse.`\n            : outcome.browserClosed\n              ? `Page \"${input.pageId}\" closed. Browser \"${browserId}\" was also closed because no pages remained.`\n              : `Page \"${input.pageId}\" closed successfully`,\n      });\n    });\n  }\n}\n","import type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const CreateProfileToolInputSchema = z.object({\n  name: z.string().describe('Unique profile name'),\n  browserType: z\n    .enum(['chromium', 'firefox', 'webkit'])\n    .optional()\n    .default('chromium')\n    .describe('Preferred browser type for the profile'),\n  viewport: z\n    .object({\n      width: z.number(),\n      height: z.number(),\n    })\n    .optional(),\n  userAgent: z.string().optional(),\n  locale: z.string().optional(),\n  timezone: z.string().optional(),\n  colorScheme: z.enum(['light', 'dark', 'no-preference']).optional(),\n});\n\nexport class CreateProfileTool implements Tool<CreateProfileToolInput> {\n  static readonly TOOL_NAME = 'browser_create_profile';\n\n  constructor(private readonly profileService: IProfileService) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return CreateProfileToolInputSchema;\n  }\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: z.toJSONSchema(CreateProfileToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const DeleteProfileToolInputSchema = z.object({\n  name: z.string().describe('Name of the profile to delete'),\n});\n\nexport class DeleteProfileTool implements Tool<DeleteProfileToolInput> {\n  static readonly TOOL_NAME = 'browser_delete_profile';\n\n  constructor(private readonly profileService: IProfileService) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return DeleteProfileToolInputSchema;\n  }\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: z.toJSONSchema(DeleteProfileToolInputSchema, { reused: 'inline' }),\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 { isAbsolute } from 'node:path';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const DiscoverSpecsToolInputSchema = z.object({\n  basePath: z.string().describe('Absolute path to search for e2e projects (required)'),\n  projectName: z.string().optional().describe(\"Filter by project name (e.g., 'boomlink-e2e')\"),\n  specPattern: z\n    .string()\n    .optional()\n    .describe('Filter specs by name pattern (supports * and ? wildcards, e.g., \"auth*\", \"*.spec.ts\")'),\n  includeMetadata: z\n    .boolean()\n    .optional()\n    .default(true)\n    .describe('Include metadata like hasArgsSchema detection (default: true)'),\n});\n\nexport class DiscoverSpecsTool implements Tool<DiscoverSpecsToolInput> {\n  static readonly TOOL_NAME = 'discover_specs';\n\n  constructor(private specDiscoveryService: ISpecDiscoveryService) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return DiscoverSpecsToolInputSchema;\n  }\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: z.toJSONSchema(DiscoverSpecsToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nconst TargetSchema = ElementSelectorSchema.extend({\n  x: z.number().optional().describe('X coordinate'),\n  y: z.number().optional().describe('Y coordinate'),\n});\n\nexport const DragToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  source: ElementSelectorSchema.describe('Source element selector'),\n  target: TargetSchema.describe('Target element selector or coordinates'),\n  force: z.boolean().optional().default(false).describe('Bypass actionability checks'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class DragTool extends BaseTool<DragToolInput> {\n  static readonly TOOL_NAME = 'browser_drag';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return DragToolInputSchema;\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: z.toJSONSchema(DragToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const EmulateToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  geolocation: z\n    .object({\n      latitude: z.number().min(-90).max(90),\n      longitude: z.number().min(-180).max(180),\n      accuracy: z.number().min(0).optional(),\n    })\n    .optional()\n    .describe('Geolocation coordinates to emulate'),\n  locale: z.string().optional().describe('Locale to emulate (e.g., \"en-US\", \"de-DE\")'),\n  timezone: z.string().optional().describe('Timezone to emulate (e.g., \"America/New_York\", \"Europe/London\")'),\n  userAgent: z.string().optional().describe('User agent string to use'),\n  offline: z.boolean().optional().describe('Whether to emulate offline mode'),\n  colorScheme: z.enum(['light', 'dark', 'no-preference']).optional().describe('Color scheme preference'),\n  reducedMotion: z.enum(['reduce', 'no-preference']).optional().describe('Reduced motion preference'),\n});\n\nexport class EmulateTool extends BaseTool<EmulateToolInput> {\n  static readonly TOOL_NAME = 'browser_emulate';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return EmulateToolInputSchema;\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: z.toJSONSchema(EmulateToolInputSchema, { reused: 'inline' }),\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 { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { browserOutputPath } from '../utils/ownerContext.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\nexport const EvaluateScriptToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  script: z\n    .string()\n    .describe(\n      'JavaScript code to execute. Can be a function body or expression (e.g., \"document.title\" or \"() => window.location.href\")',\n    ),\n  arg: z.unknown().optional().describe('Optional argument to pass to the script if it is a function'),\n});\n\nexport class EvaluateScriptTool extends BaseTool<EvaluateScriptToolInput> {\n  static readonly TOOL_NAME = 'browser_evaluate_script';\n  private static readonly INLINE_SIZE_THRESHOLD = 10_000;\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return EvaluateScriptToolInputSchema;\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: z.toJSONSchema(EvaluateScriptToolInputSchema, { reused: 'inline' }),\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        if (Buffer.byteLength(serializedResult, 'utf-8') <= EvaluateScriptTool.INLINE_SIZE_THRESHOLD) {\n          return this.success(serializedResult);\n        }\n\n        // Save large serialized result to temp file\n        const filename = `evaluate-${randomUUID()}.json`;\n        const filePath = await browserOutputPath(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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport { ExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.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\nexport const ExpectToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  selector: z\n    .string()\n    .optional()\n    .describe(\n      'CSS selector for the element to assert on (not required for page-level assertions like toHaveTitle, toHaveURL)',\n    ),\n  assertion: z\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    .describe('Type of assertion to perform'),\n  expected: z\n    .union([z.string(), z.number()])\n    .optional()\n    .describe('Expected value for assertions that require one (e.g., toHaveText, toHaveValue)'),\n  attributeName: z.string().optional().describe('Attribute name for toHaveAttribute or toHaveCSS assertion'),\n  not: z.boolean().optional().describe('Whether to negate the assertion (not.toBeVisible, etc.)'),\n  timeout: z.number().optional().describe('Timeout in milliseconds for the assertion (default: 5000)'),\n});\n\nexport class ExpectTool extends BaseTool<ExpectToolInput> {\n  static readonly TOOL_NAME = 'browser_expect';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly extensionTaskQueue: ExtensionTaskQueue,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ExpectToolInputSchema;\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: z.toJSONSchema(ExpectToolInputSchema, { reused: 'inline' }),\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              // Built per call so concurrent agents cannot retarget each other.\n              const extensionPageProxy = new ExtensionPageProxy(this.extensionTaskQueue);\n              extensionPageProxy.setTarget(pageEntry.id, pageEntry.browserId);\n              return 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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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';\nimport { resolveLocatorCandidates } from './ResolveLocatorTool.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\nexport const FillToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  value: z.string().describe('The text value to fill into the input'),\n  force: z.boolean().optional().default(false).describe('Bypass actionability checks'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class FillTool extends BaseTool<FillToolInput> {\n  static readonly TOOL_NAME = 'browser_fill';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return FillToolInputSchema;\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: z.toJSONSchema(FillToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: FillToolInput): Promise<CallToolResult> {\n    // Unbound CLI, shared daemon, extension and VM paths cannot resolve credentials.\n    if (/\\$\\{\\s*secrets\\b|\\{\\{\\s*secrets\\b/.test(input.value)) {\n      return this.error('Secret templates require an authorized profile-bound Playwright runtime');\n    }\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 selector = this.buildSelector(input);\n\n        try {\n          const locator = await this.elementLocator.locate(page, selector);\n          await locator.fill(input.value, { force: input.force, timeout: input.timeout });\n          return this.success(`Filled element with: \"${input.value}\"`);\n        } catch (error) {\n          const message = error instanceof Error ? error.message : String(error);\n          if (!message.includes('Element not found')) {\n            throw error;\n          }\n\n          const candidates = await resolveLocatorCandidates(page, selector, 5);\n          return this.error(\n            JSON.stringify(\n              {\n                error: message,\n                pageId: input.pageId,\n                selector,\n                ...candidates,\n              },\n              null,\n              2,\n            ),\n          );\n        }\n      },\n    );\n  }\n\n  private buildSelector(input: FillToolInput): ElementSelector {\n    return {\n      uid: input.uid,\n      role: input.role,\n      name: input.name,\n      label: input.label,\n      placeholder: input.placeholder,\n      testId: input.testId,\n      text: input.text,\n      exact: input.exact,\n      selector: input.selector,\n      xpath: input.xpath,\n      frame: input.frame,\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const GetNetworkRequestToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  requestId: z.string().describe('The ID of the network request to retrieve (e.g., \"req-1\")'),\n});\n\nexport class GetNetworkRequestTool extends BaseTool<GetNetworkRequestToolInput> {\n  static readonly TOOL_NAME = 'browser_get_network_request';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly pageMonitor: IPageMonitorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return GetNetworkRequestToolInputSchema;\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: z.toJSONSchema(GetNetworkRequestToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const GoBackToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  waitUntil: z\n    .enum(['load', 'domcontentloaded', 'networkidle', 'commit'])\n    .optional()\n    .default('load')\n    .describe('Wait until condition'),\n  timeout: z.number().optional().default(DEFAULT_TOOL_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class GoBackTool extends BaseTool<GoBackToolInput> {\n  static readonly TOOL_NAME = 'browser_go_back';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return GoBackToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: GoBackTool.TOOL_NAME,\n      description: 'Navigates the browser back in history.',\n      inputSchema: z.toJSONSchema(GoBackToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const GoForwardToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  waitUntil: z\n    .enum(['load', 'domcontentloaded', 'networkidle', 'commit'])\n    .optional()\n    .default('load')\n    .describe('Wait until condition'),\n  timeout: z.number().optional().default(DEFAULT_TOOL_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class GoForwardTool extends BaseTool<GoForwardToolInput> {\n  static readonly TOOL_NAME = 'browser_go_forward';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return GoForwardToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: GoForwardTool.TOOL_NAME,\n      description: 'Navigates the browser forward in history.',\n      inputSchema: z.toJSONSchema(GoForwardToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport type { Dialog } from 'playwright';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const HandleDialogToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  accept: z.boolean().describe('Whether to accept (true) or dismiss (false) the dialog'),\n  promptText: z.string().optional().describe('Text to enter for prompt dialogs (only used when accept is true)'),\n  timeout: z\n    .number()\n    .optional()\n    .default(DEFAULT_TOOL_TIMEOUT_MS)\n    .describe(`Timeout in milliseconds to wait for dialog (default: ${DEFAULT_TOOL_TIMEOUT_MS})`),\n});\n\nexport class HandleDialogTool extends BaseTool<HandleDialogToolInput> {\n  static readonly TOOL_NAME = 'browser_handle_dialog';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return HandleDialogToolInputSchema;\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: z.toJSONSchema(HandleDialogToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nexport const HoverToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  position: z.object({ x: z.number(), y: z.number() }).optional().describe('Position offset within element'),\n  modifiers: z\n    .array(z.enum(['Alt', 'Control', 'Meta', 'Shift']))\n    .optional()\n    .describe('Modifier keys to hold'),\n  force: z.boolean().optional().default(false).describe('Bypass actionability checks'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class HoverTool extends BaseTool<HoverToolInput> {\n  static readonly TOOL_NAME = 'browser_hover';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return HoverToolInputSchema;\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: z.toJSONSchema(HoverToolInputSchema, { reused: 'inline' }),\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","/// <reference lib=\"dom\" />\n\n/**\n * InspectElementTool\n *\n * DESIGN PATTERNS:\n * - Extends BaseTool for common functionality\n * - Service delegation for element location\n * - Returns structured JSON diagnostics for agent recovery workflows\n *\n * CODING STANDARDS:\n * - Use TOOL_NAME constant with snake_case\n * - Avoid throwing for \"not found\" outcomes when structured diagnostics are more useful\n * - Keep DOM metadata extraction self-contained\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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 InspectElementToolInput extends ElementSelector, PageTargetInput {}\n\nexport const InspectElementToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n});\n\ninterface ElementInspection {\n  tagName: string;\n  id: string;\n  className: string;\n  role: string;\n  accessibleName: string;\n  label: string | null;\n  placeholder: string | null;\n  testId: string | null;\n  textContent: string;\n  value: string | null;\n  visible: boolean;\n  enabled: boolean;\n  rect: {\n    x: number;\n    y: number;\n    width: number;\n    height: number;\n  } | null;\n}\n\nexport class InspectElementTool extends BaseTool<InspectElementToolInput> {\n  static readonly TOOL_NAME = 'browser_inspect_element';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return InspectElementToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: InspectElementTool.TOOL_NAME,\n      description:\n        'Inspects a matching element and returns structured metadata such as role, accessible name, label, visibility, and bounds. Use this for selector debugging and agent recovery.',\n      inputSchema: z.toJSONSchema(InspectElementToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: InspectElementToolInput): Promise<CallToolResult> {\n    return this.safeExecute(async () =>\n      this.executeWithMode(\n        InspectElementTool.TOOL_NAME,\n        input.pageId,\n        input as unknown as Record<string, unknown>,\n        async () => {\n          const page = this.resolvePage(input.pageId);\n          const selector = this.buildSelector(input);\n          const locator = await this.elementLocator.locateAll(page, selector);\n          const matchCount = await locator.count();\n\n          if (matchCount === 0) {\n            return this.successJson({\n              pageId: input.pageId,\n              selector,\n              matched: false,\n              matchCount: 0,\n              element: null,\n            });\n          }\n\n          const firstMatch = locator.first();\n          const [visible, enabled, rect, element] = await Promise.all([\n            firstMatch.isVisible().catch(() => false),\n            firstMatch.isEnabled().catch(() => false),\n            firstMatch.boundingBox().catch(() => null),\n            firstMatch.evaluate((node) => {\n              const normalizeText = (value: string | null | undefined): string =>\n                (value ?? '').replace(/\\s+/g, ' ').trim();\n              const getLabelText = (element: Element): string | null => {\n                if (\n                  element instanceof HTMLInputElement ||\n                  element instanceof HTMLTextAreaElement ||\n                  element instanceof HTMLSelectElement\n                ) {\n                  if (typeof element.labels?.length === 'number' && element.labels.length > 0) {\n                    return normalizeText(element.labels[0]?.textContent);\n                  }\n\n                  if (element.id) {\n                    const explicitLabel = document.querySelector(`label[for=\"${CSS.escape(element.id)}\"]`);\n                    if (explicitLabel?.textContent) {\n                      return normalizeText(explicitLabel.textContent);\n                    }\n                  }\n                }\n\n                const wrappedLabel = element.closest('label');\n                return wrappedLabel?.textContent ? normalizeText(wrappedLabel.textContent) : null;\n              };\n              const getAccessibleName = (element: Element): string => {\n                const ariaLabel = element.getAttribute('aria-label');\n                if (ariaLabel) {\n                  return normalizeText(ariaLabel);\n                }\n\n                const labelledBy = element.getAttribute('aria-labelledby');\n                if (labelledBy) {\n                  const labels = labelledBy\n                    .split(/\\s+/)\n                    .map((id) => normalizeText(document.getElementById(id)?.textContent))\n                    .filter(Boolean);\n                  if (labels.length > 0) {\n                    return labels.join(' ');\n                  }\n                }\n\n                const labelText = getLabelText(element);\n                if (labelText) {\n                  return labelText;\n                }\n\n                if (element instanceof HTMLInputElement && element.placeholder) {\n                  return normalizeText(element.placeholder);\n                }\n\n                if (element instanceof HTMLImageElement && element.alt) {\n                  return normalizeText(element.alt);\n                }\n\n                return normalizeText(element.textContent);\n              };\n              const getRole = (element: Element): string => {\n                const ariaRole = element.getAttribute('role');\n                if (ariaRole) {\n                  return ariaRole;\n                }\n\n                if (element instanceof HTMLInputElement) {\n                  const type = (element.type || 'text').toLowerCase();\n                  const inputRoles: Record<string, string> = {\n                    button: 'button',\n                    checkbox: 'checkbox',\n                    email: 'textbox',\n                    number: 'spinbutton',\n                    password: 'textbox',\n                    radio: 'radio',\n                    range: 'slider',\n                    search: 'searchbox',\n                    submit: 'button',\n                    tel: 'textbox',\n                    text: 'textbox',\n                    url: 'textbox',\n                  };\n                  return inputRoles[type] || 'textbox';\n                }\n\n                const tagName = element.tagName.toLowerCase();\n                const roleMap: Record<string, string> = {\n                  a: element.hasAttribute('href') ? 'link' : 'none',\n                  button: 'button',\n                  img: 'img',\n                  option: 'option',\n                  select: 'combobox',\n                  textarea: 'textbox',\n                };\n\n                return roleMap[tagName] || 'none';\n              };\n              const value =\n                node instanceof HTMLInputElement ||\n                node instanceof HTMLTextAreaElement ||\n                node instanceof HTMLSelectElement\n                  ? node.value\n                  : null;\n\n              return {\n                tagName: node.tagName.toLowerCase(),\n                id: node.id || '',\n                className: node.getAttribute('class') || '',\n                role: getRole(node),\n                accessibleName: getAccessibleName(node),\n                label: getLabelText(node),\n                placeholder: node.getAttribute('placeholder'),\n                testId: node.getAttribute('data-testid') || node.getAttribute('data-test-id'),\n                textContent: normalizeText(node.textContent).slice(0, 500),\n                value,\n              };\n            }),\n          ]);\n\n          const inspection: ElementInspection = {\n            ...element,\n            visible,\n            enabled,\n            rect: rect\n              ? {\n                  x: rect.x,\n                  y: rect.y,\n                  width: rect.width,\n                  height: rect.height,\n                }\n              : null,\n          };\n\n          return this.successJson({\n            pageId: input.pageId,\n            selector,\n            matched: true,\n            matchCount,\n            element: inspection,\n          });\n        },\n      ),\n    );\n  }\n\n  private buildSelector(input: InspectElementToolInput): ElementSelector {\n    return {\n      uid: input.uid,\n      role: input.role,\n      name: input.name,\n      label: input.label,\n      placeholder: input.placeholder,\n      testId: input.testId,\n      text: input.text,\n      exact: input.exact,\n      selector: input.selector,\n      xpath: input.xpath,\n      frame: input.frame,\n    };\n  }\n}\n","/**\n * Keep the user's frontmost app in front while a headed browser launches.\n *\n * macOS raises a freshly spawned GUI process (Chrome, Playwright Chromium) to the\n * front. We note the frontmost app and the apps already running, and when an app\n * that started during the launch takes the front, we hand focus back. Switching to\n * an app that was already running is the user's choice and is left alone.\n * Other platforms do nothing.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\nconst ASN_PATTERN = /ASN:0x[0-9a-f]+-0x[0-9a-f]+/gi;\n\nasync function lsappinfo(...args: string[]): Promise<string> {\n  return (await execFileAsync('lsappinfo', args)).stdout;\n}\n\nasync function frontAsn(): Promise<string | undefined> {\n  return (await lsappinfo('front').catch(() => '')).match(ASN_PATTERN)?.[0];\n}\n\nasync function activateAsn(asn: string): Promise<void> {\n  const pid = Number((await lsappinfo('info', '-only', 'pid', asn).catch(() => '')).match(/=\\s*(\\d+)/)?.[1]);\n  if (!Number.isInteger(pid) || pid <= 0) return;\n  // NSRunningApplication.activate needs no Automation permission, unlike `tell application ... to activate`.\n  const script = `ObjC.import(\"AppKit\"); $.NSRunningApplication.runningApplicationWithProcessIdentifier(${pid}).activateWithOptions(2)`;\n  await execFileAsync('osascript', ['-l', 'JavaScript', '-e', script]).catch(() => undefined);\n}\n\n/**\n * Run `launch` and hand focus back whenever a newly started app takes the front,\n * polling during the launch so a new window holds focus for at most one poll.\n * ponytail: the window can still flash in front for ~250 ms. Launching through `open -g`\n * would avoid that, but it loses the ChildProcess handle the browser lifecycle depends on.\n */\nexport async function withPreservedFocus<T>(launch: () => Promise<T>): Promise<T> {\n  if (process.platform !== 'darwin') {\n    return launch();\n  }\n  const before = await frontAsn();\n  if (!before) {\n    return launch();\n  }\n  const running = new Set((await lsappinfo('visibleProcessList').catch(() => '')).match(ASN_PATTERN) ?? []);\n  running.add(before);\n  const restoreIfStolen = async (): Promise<void> => {\n    const current = await frontAsn();\n    if (current && !running.has(current)) {\n      await activateAsn(before);\n    }\n  };\n\n  let polling = Promise.resolve();\n  const timer = setInterval(() => {\n    polling = polling.then(restoreIfStolen);\n  }, 250);\n  try {\n    return await launch();\n  } finally {\n    clearInterval(timer);\n    await polling;\n    await restoreIfStolen();\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 { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { type ExtensionLinkTokens, LINK_TOKEN_FRAGMENT_KEY, redactLinkTokens } from '../server/extensionLinkTokens.js';\nimport { hasDisplay, type IBrowserService } from '../services/BrowserService.js';\nimport { type IProxyConfigService, ProxyConfigService, type ProxyInput } from '../services/ProxyConfigService.js';\nimport type { IStealthLauncher } from '../services/StealthLauncher.js';\nimport type { BrowseOptions, Tool, ToolDefinition } from '../types/index.js';\nimport { buildPlaywrightBaseUrl, DEFAULT_MCP_HOST, DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport { getCurrentProfileName } from '../utils/ownerContext.js';\nimport { withPreservedFocus } from '../utils/preserveFocus.js';\nimport type { ProxyConfig } from '../utils/proxyAuthExtension.js';\nimport { resolveWorkspaceRoot } from '../utils/workspaceRoot.js';\n\nconst ABOUT_BLANK_URL = 'about:blank';\n/** Where a Po server mounts the browse API that serves the extension link. */\nconst PO_EXTENSION_LINK_PATH = '/api/plugins/browse';\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\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve the main worktree root. Returns null if not in a linked worktree.\n */\nasync function resolveGitCommonRoot(startPath: string): Promise<string | null> {\n  try {\n    const { stdout } = await execFileAsync('git', ['rev-parse', '--git-common-dir'], { cwd: startPath });\n    const gitCommonDir = stdout.trim();\n    const resolved = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.join(startPath, gitCommonDir);\n    const repoRoot = path.dirname(resolved);\n    return repoRoot !== startPath ? repoRoot : null;\n  } catch {\n    return null;\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  /** Docker browser runtime for vm mode: 'cloakbrowser' (default) or 'chrome-for-testing' */\n  vmDockerBrowser?: 'cloakbrowser' | 'chrome-for-testing';\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?: ProxyInput;\n}\n\ntype ResolvedLaunchBrowserToolInput = Omit<LaunchBrowserToolInput, 'proxy'> & {\n  proxy?: ProxyConfig;\n};\n\nconst proxyConfigSchema = z.object({\n  server: z.string().describe('Proxy server URL (e.g., \"http://proxy.example.com:8080\")'),\n  username: z.string().optional().describe('Proxy username for authentication'),\n  password: z.string().optional().describe('Proxy password for authentication'),\n  bypass: z\n    .string()\n    .optional()\n    .describe('Comma-separated list of hosts to bypass the proxy (e.g., \"localhost,127.0.0.1\")'),\n});\n\nexport const LaunchBrowserToolInputSchema = z.object({\n  mode: z\n    .enum(['playwright', 'extension', 'vm', 'stealth'])\n    .optional()\n    .describe(\n      'Execution mode. Unset: \"extension\", or \"playwright\" when headless is true; falls back to \"playwright\" with a warning when the extension cannot start. \"playwright\" uses Playwright APIs. \"extension\" drives Chrome through the browse-tool extension (headed). \"vm\" launches in an isolated runtime (for example via Docker). \"stealth\" spawns Chrome directly without automation flags.',\n    ),\n  browserType: z\n    .enum(['chromium', 'firefox', 'webkit'])\n    .optional()\n    .default('chromium')\n    .describe('Browser type to use (only applies to playwright mode)'),\n  headless: z\n    .boolean()\n    .optional()\n    .describe(\n      'Run browser in headless mode (only applies to playwright mode; true also selects playwright when mode is unset). Unset: the profile setting, else headless for the playwright engine and headed for the cloak engine',\n    ),\n  url: z.string().optional().describe('Optional URL to navigate to after launch'),\n  profileName: z\n    .string()\n    .optional()\n    .describe('Optional profile name to use for browser settings and persistent storage'),\n  vmCommand: z\n    .string()\n    .optional()\n    .describe('Executable/alias for vm mode launch (e.g., \"docker\" for isolated container runtime)'),\n  vmCommandArgs: z.array(z.string()).optional().describe('Browser arguments for vm command runtime'),\n  vmDockerBrowser: z\n    .enum(['cloakbrowser', 'chrome-for-testing'])\n    .optional()\n    .describe(\n      'Docker browser runtime for vm mode. \"cloakbrowser\" (default) uses stealth CloakBrowser with C++ anti-detection patches. \"chrome-for-testing\" uses Google Chrome for Testing.',\n    ),\n  vmBrowserId: z.string().optional().describe('Optional browserId override for vm mode'),\n  startupDelayMs: z.number().optional().describe('Optional startup delay in milliseconds before returning in vm mode'),\n  vmServerUrl: z\n    .string()\n    .optional()\n    .describe(\n      'Explicit MCP server URL reachable from inside VM (e.g., \"http://host.docker.internal:3200\"). Overrides registry discovery.',\n    ),\n  vmExtensionPath: z\n    .string()\n    .optional()\n    .describe(\n      'Explicit extension path that exists inside VM filesystem. Useful when VM does not share host workspace paths.',\n    ),\n  vmEnableVnc: z.boolean().optional().default(false).describe('Expose VNC/noVNC ports when using Docker VM runtime'),\n  vmVncPort: z\n    .number()\n    .min(1)\n    .max(65535)\n    .optional()\n    .describe('Host port for VNC (container port 5900) when vmEnableVnc is true'),\n  vmNoVncPort: z\n    .number()\n    .min(1)\n    .max(65535)\n    .optional()\n    .describe('Host port for noVNC web UI (container port 7900) when vmEnableVnc is true'),\n  vmVncPassword: z.string().optional().describe('Optional VNC password for Docker VM runtime'),\n  baseURL: z\n    .string()\n    .optional()\n    .describe('Base URL for relative navigation (e.g., page.goto(\"/\") will use this as the base)'),\n  videoDir: z\n    .string()\n    .optional()\n    .describe(\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: z\n    .union([proxyConfigSchema, z.literal(true)])\n    .optional()\n    .describe(\n      'Proxy configuration for routing browser traffic. Pass an object for explicit proxy settings, or true to load session.yaml from the proxy config directory.',\n    ),\n});\n\n/**\n * Tool for launching a new browser instance.\n * Returns browserId and pageId for subsequent browser operations.\n */\nexport class LaunchBrowserTool implements Tool<LaunchBrowserToolInput> {\n  static readonly TOOL_NAME = 'browser_launch';\n\n  constructor(\n    private readonly browserService: IBrowserService,\n    private readonly stealthLauncher: IStealthLauncher,\n    private readonly proxyConfigService: IProxyConfigService = new ProxyConfigService(),\n    private readonly options: BrowseOptions = {},\n    private readonly linkTokens?: ExtensionLinkTokens,\n  ) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return LaunchBrowserToolInputSchema;\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. Defaults to extension mode (headed, bot-detection-free) and to playwright when headless is true. Use mode \"vm\" for isolated runtime launch.',\n      inputSchema: z.toJSONSchema(LaunchBrowserToolInputSchema, { reused: 'inline' }),\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(requestedInput: LaunchBrowserToolInput): Promise<CallToolResult> {\n    const explicitMode = requestedInput.mode;\n    // Extensions need a headed Chrome, so a headless request picks the playwright engine.\n    const mode = explicitMode ?? (requestedInput.headless ? 'playwright' : 'extension');\n    // A request bound to a profile (x-browse-profile) may only launch that identity.\n    const boundProfile = getCurrentProfileName();\n    if (boundProfile && requestedInput.profileName && requestedInput.profileName !== boundProfile) {\n      return {\n        content: [\n          {\n            type: 'text',\n            text: `This session is bound to browser profile \"${boundProfile}\" and cannot launch profile \"${requestedInput.profileName}\". Omit profileName.`,\n          },\n        ],\n        isError: true,\n      };\n    }\n    const input = boundProfile ? { ...requestedInput, profileName: boundProfile } : requestedInput;\n\n    try {\n      const launchInput: ResolvedLaunchBrowserToolInput = {\n        ...input,\n        proxy: this.proxyConfigService.resolve({\n          proxy: input.proxy,\n          profileName: input.profileName,\n        }),\n      };\n\n      // A headed launch must not pull the browser in front of what the user is doing.\n      return await withPreservedFocus(() => {\n        if (mode === 'stealth') {\n          return this.launchStealthMode(launchInput);\n        }\n        if (mode === 'vm') {\n          return this.launchVmMode(launchInput);\n        }\n        if (mode === 'extension') {\n          return explicitMode ? this.launchExtensionMode(launchInput) : this.launchDefaultExtensionMode(launchInput);\n        }\n        return this.launchPlaywrightMode(launchInput);\n      });\n    } catch (error) {\n      const message = this.formatLaunchError(error);\n      return {\n        content: [{ type: 'text', text: message }],\n        isError: true,\n      };\n    }\n  }\n\n  private formatLaunchError(error: unknown): string {\n    // Launch errors can quote the bootstrap URL, which carries the link token.\n    const message = redactLinkTokens(error instanceof Error ? error.message : String(error));\n    if (!/Browser session conflict|already in use|profile \".*\" is busy/i.test(message)) {\n      return message;\n    }\n\n    return `${message}\\n\\nThis is a browser automation session conflict, not an application failure. List your pages (browser.pages() in code mode, browser_list_pages otherwise) to see reusable browserId/pageId values, retry after the active launch completes, or launch with a different profileName.`;\n  }\n\n  /**\n   * The default launch: extension mode, falling back to playwright with a warning\n   * when the extension cannot start, so changing the default never fails a launch.\n   * ponytail: a profile whose saved setting is headless still opens a headed extension\n   * window when headless is unset; resolve the profile setting here if that matters.\n   */\n  private async launchDefaultExtensionMode(input: ResolvedLaunchBrowserToolInput): Promise<CallToolResult> {\n    let reason = hasDisplay() ? undefined : 'no display is available';\n    if (!reason) {\n      try {\n        return await this.launchExtensionMode(input);\n      } catch (error) {\n        const message = error instanceof Error ? error.message : String(error);\n        // A session conflict would hit playwright too; report it instead of masking it.\n        if (/Browser session conflict|already in use|profile \".*\" is busy/i.test(message)) {\n          throw error;\n        }\n        reason = message;\n      }\n    }\n    return this.launchPlaywrightMode(\n      input,\n      `Extension launch unavailable (${reason}); launched in playwright mode instead. Pass mode \"extension\" to require it.`,\n    );\n  }\n\n  /**\n   * Launch browser in Playwright mode (original behavior)\n   */\n  private async launchPlaywrightMode(\n    input: ResolvedLaunchBrowserToolInput,\n    fallbackWarning?: string,\n  ): 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              ...(launchResult.warning || fallbackWarning\n                ? { warning: [fallbackWarning, launchResult.warning].filter(Boolean).join(' ') }\n                : {}),\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: ResolvedLaunchBrowserToolInput): 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: instance.pageId,\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: ResolvedLaunchBrowserToolInput): Promise<CallToolResult> {\n    if (DEBUG_EXTENSION_RECORDING_STDOUT) {\n      console.warn(\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, this.linkTokenFor(browserId));\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: ResolvedLaunchBrowserToolInput): 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 explicitServer = input.vmServerUrl ?? this.options.vm?.serverUrl;\n    const token = explicitServer ? undefined : this.linkTokenFor(browserId);\n    const bootstrapUrl = this.buildVmBootstrapUrl(serverUrl, browserId, navigateUrl, token);\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 ?? this.options.vm?.extensionPath,\n      mode: 'vm',\n      vmDockerBrowser: input.vmDockerBrowser,\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   * The link token goes in the fragment, which browsers never send to a server.\n   */\n  private buildVmBootstrapUrl(serverUrl: string, browserId: string, navigateUrl: string, token?: 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    if (token) {\n      bootstrapUrl.hash = new URLSearchParams({ [LINK_TOKEN_FRAGMENT_KEY]: token }).toString();\n    }\n\n    return bootstrapUrl.toString();\n  }\n\n  /**\n   * The link token for a launch against a Po server. A running browser keeps its\n   * token, since the launch reuses it; any other launch gets a fresh one.\n   */\n  private linkTokenFor(browserId: string): string | undefined {\n    if (!this.linkTokens || !this.serverOrigin()) return undefined;\n    const reused = this.browserService.getBrowser(browserId) ? this.linkTokens.current(browserId) : undefined;\n    return reused ?? this.linkTokens.issue(browserId);\n  }\n\n  private async resolveVmBootstrapServerUrl(inputVmServerUrl?: string, vmCommand?: string): Promise<string> {\n    const explicit = inputVmServerUrl ?? this.options.vm?.serverUrl;\n    if (explicit) {\n      return this.normalizeVmServerUrl(explicit);\n    }\n\n    const serverOrigin = this.serverOrigin();\n    if (serverOrigin) {\n      const url = new URL(serverOrigin);\n      if (this.isDockerVmCommand(vmCommand ?? 'docker')) {\n        url.hostname = DEFAULT_DOCKER_VM_SERVER_HOST;\n      }\n      return `${url.origin}${PO_EXTENSION_LINK_PATH}`;\n    }\n\n    const host = this.options.registry?.host ?? DEFAULT_MCP_HOST;\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 server URL the extension connects back to. With `serverOrigin`\n   * it is the Po browse API on that origin and the port registry is not read.\n   * Otherwise it is the daemon's registered port for this workspace, falling\n   * back to the default host/port when the registry has no entry.\n   */\n  private async resolveExtensionBootstrapServerUrl(): Promise<string> {\n    const serverOrigin = this.serverOrigin();\n    if (serverOrigin) {\n      return `${serverOrigin}${PO_EXTENSION_LINK_PATH}`;\n    }\n    const host = this.options.registry?.host ?? DEFAULT_MCP_HOST;\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  /** The configured origin the extension connects back to, without a trailing slash. */\n  private serverOrigin(): string | undefined {\n    const configured = this.options.serverOrigin;\n    const origin = typeof configured === 'function' ? configured() : configured;\n    return origin ? origin.replace(/\\/+$/, '') : undefined;\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   * Resolve the MCP HTTP endpoint from the port registry using a scoped fallback chain:\n   * 1. Current repositoryPath (worktree or repo root)\n   * 2. Main worktree root (if running inside a linked git worktree)\n   * 3. Global only when explicitly enabled for compatibility\n   */\n  private async getRegisteredPort(host: string): Promise<{ host: string; port: number }> {\n    const registry = this.options.registry ?? {};\n    const fallback = { host, port: registry.port ?? DEFAULT_MCP_PORT };\n    const repositoryPath = this.options.workspaceRoot ?? resolveWorkspaceRoot({ env: {} });\n    const environment = registry.environment || DEFAULT_ENVIRONMENT;\n    const portRegistry = new PortRegistryService(registry.portRegistryPath);\n    const baseQuery = {\n      serviceName: PLAYWRIGHT_HTTP_SERVICE_NAME,\n      serviceType: PLAYWRIGHT_HTTP_SERVICE_TYPE,\n      environment,\n    };\n\n    const tryGetPort = async (repoPath: string): Promise<{ host: string; port: number } | null> => {\n      try {\n        const result = await portRegistry.getPort({ ...baseQuery, repositoryPath: repoPath });\n        if (result.success && result.record && this.isValidPort(result.record.port)) {\n          return { host: result.record.host || host, port: result.record.port };\n        }\n      } catch (error) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        console.warn(`${LOG_PREFIX} Registry lookup failed (path=${repoPath}): ${errorMessage}`);\n      }\n      return null;\n    };\n\n    // 1. Current path (worktree or main repo)\n    const scoped = await tryGetPort(repositoryPath);\n    if (scoped) return scoped;\n\n    // 2. Main worktree root\n    const gitCommonRoot = await resolveGitCommonRoot(repositoryPath);\n    if (gitCommonRoot) {\n      const fromRoot = await tryGetPort(gitCommonRoot);\n      if (fromRoot) return fromRoot;\n    }\n\n    if (registry.allowGlobalReuse) {\n      try {\n        const allocations = await portRegistry.listAllocations(baseQuery);\n        const sorted = allocations\n          .filter((r) => this.isValidPort(r.port))\n          .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n        const record = sorted[0];\n        if (record) return { host: record.host || host, port: record.port };\n      } catch (error) {\n        const errorMessage = error instanceof Error ? error.message : String(error);\n        console.warn(`${LOG_PREFIX} Global registry lookup failed: ${errorMessage}`);\n      }\n    }\n\n    return fallback;\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 const CONSOLE_MESSAGE_TYPES = [\n  'log',\n  'info',\n  'warning',\n  'error',\n  'debug',\n  'trace',\n  'dir',\n  'dirxml',\n  'table',\n  'count',\n  'assert',\n] as const;\n\nexport type ConsoleMessageType = (typeof CONSOLE_MESSAGE_TYPES)[number];\n\nexport interface ListConsoleMessagesToolInput extends PageTargetInput {\n  /** Filter by message type (log, info, warning, error, debug, etc.) */\n  type?: ConsoleMessageType;\n  /** Maximum number of messages to return */\n  limit?: number;\n}\n\nexport const ListConsoleMessagesToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  type: z\n    .enum(CONSOLE_MESSAGE_TYPES)\n    .optional()\n    .describe('Filter by one supported console type. Omit this field to return all console messages.'),\n  limit: z.number().min(1).optional().describe('Maximum number of messages to return'),\n});\n\nexport class ListConsoleMessagesTool extends BaseTool<ListConsoleMessagesToolInput> {\n  static readonly TOOL_NAME = 'browser_list_console_messages';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly pageMonitor: IPageMonitorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ListConsoleMessagesToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: ListConsoleMessagesTool.TOOL_NAME,\n      description:\n        'Lists console messages captured during page automation. Omit type to return all messages. Requires monitoring to be started first.',\n      inputSchema: z.toJSONSchema(ListConsoleMessagesToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const ListNetworkRequestsToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  resourceType: z\n    .string()\n    .optional()\n    .describe('Filter by resource type (document, xhr, fetch, script, stylesheet, image, etc.)'),\n  urlPattern: z.string().optional().describe('Filter by URL pattern (substring match)'),\n  method: z.string().optional().describe('Filter by HTTP method (GET, POST, etc.)'),\n  limit: z.number().min(1).optional().describe('Maximum number of requests to return'),\n});\n\nexport class ListNetworkRequestsTool extends BaseTool<ListNetworkRequestsToolInput> {\n  static readonly TOOL_NAME = 'browser_list_network_requests';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly pageMonitor: IPageMonitorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ListNetworkRequestsToolInputSchema;\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: z.toJSONSchema(ListNetworkRequestsToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { isOwnedByOtherAgent } from '../utils/ownerContext.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface ListPagesToolInput {\n  /** Browser ID to list pages for */\n  browserId: string;\n}\n\nexport const ListPagesToolInputSchema = z.object({\n  browserId: z.string().describe('Browser ID to list pages for'),\n});\n\nexport class ListPagesTool extends BaseTool<ListPagesToolInput> {\n  static readonly TOOL_NAME = 'browser_list_pages';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ListPagesToolInputSchema;\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: z.toJSONSchema(ListPagesToolInputSchema, { reused: 'inline' }),\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        // Show this agent's tabs plus unowned ones. Another agent's tabs are\n        // hidden rather than refused, so listing stays useful on a shared browser.\n        const pages = this.pageRegistry.findByBrowser(input.browserId).filter((entry) => !isOwnedByOtherAgent(entry));\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { IProfileService } from '../services/ProfileService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\nexport type ListProfilesToolInput = Record<string, never>;\n\nexport const ListProfilesToolInputSchema = z.object({});\n\nexport class ListProfilesTool implements Tool<ListProfilesToolInput> {\n  static readonly TOOL_NAME = 'browser_list_profiles';\n\n  constructor(private readonly profileService: IProfileService) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ListProfilesToolInputSchema;\n  }\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: z.toJSONSchema(ListProfilesToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { ICodeSnippetService } from '../services/CodeSnippetService.js';\nimport type { Tool, ToolDefinition } from '../types/index.js';\n\nexport type ListSnippetsToolInput = Record<string, never>;\n\nexport const ListSnippetsToolInputSchema = z.object({});\n\nexport class ListSnippetsTool implements Tool<ListSnippetsToolInput> {\n  static readonly TOOL_NAME = 'browser_list_snippets';\n\n  constructor(private readonly snippetService: ICodeSnippetService) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ListSnippetsToolInputSchema;\n  }\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: z.toJSONSchema(ListSnippetsToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const NavigateToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  url: z.string().describe('URL to navigate to'),\n  waitUntil: z\n    .enum(['load', 'domcontentloaded', 'networkidle', 'commit'])\n    .optional()\n    .default('load')\n    .describe('Wait until condition'),\n  timeout: z.number().optional().default(DEFAULT_TOOL_TIMEOUT_MS).describe('Timeout in milliseconds'),\n  referer: z.string().optional().describe('Referer header value'),\n});\n\nexport class NavigateTool extends BaseTool<NavigateToolInput> {\n  static readonly TOOL_NAME = 'browser_navigate';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return NavigateToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: NavigateTool.TOOL_NAME,\n      description: 'Navigates the browser to a specified URL.',\n      inputSchema: z.toJSONSchema(NavigateToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const NewPageToolInputSchema = z.object({\n  browserId: z\n    .string()\n    .optional()\n    .describe('Browser ID to create the page in (optional - uses default browser if not specified)'),\n  url: z.string().optional().describe('Optional URL to navigate to after creation'),\n  setAsCurrent: z\n    .boolean()\n    .optional()\n    .default(true)\n    .describe('Whether to set as current page for the browser (default: true)'),\n});\n\nexport class NewPageTool extends BaseTool<NewPageToolInput> {\n  static readonly TOOL_NAME = 'browser_new_page';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return NewPageToolInputSchema;\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: z.toJSONSchema(NewPageToolInputSchema, { reused: 'inline' }),\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 { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { browserOutputPath } from '../utils/ownerContext.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\nexport const PdfToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  format: z\n    .enum(['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6'])\n    .optional()\n    .default('Letter')\n    .describe('Paper format'),\n  printBackground: z.boolean().optional().default(false).describe('Print background graphics'),\n  landscape: z.boolean().optional().default(false).describe('Landscape orientation'),\n  scale: z.number().min(0.1).max(2).optional().default(1).describe('Page scale (0.1 to 2)'),\n  marginTop: z.string().optional().describe('Top margin (e.g., \"1cm\", \"0.5in\")'),\n  marginBottom: z.string().optional().describe('Bottom margin'),\n  marginLeft: z.string().optional().describe('Left margin'),\n  marginRight: z.string().optional().describe('Right margin'),\n  filename: z.string().optional().describe('Custom filename without extension'),\n});\n\nexport class PdfTool extends BaseTool<PdfToolInput> {\n  static readonly TOOL_NAME = 'browser_pdf';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly extensionTaskQueue: ExtensionTaskQueue,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return PdfToolInputSchema;\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: z.toJSONSchema(PdfToolInputSchema, { reused: 'inline' }),\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 = await browserOutputPath(`${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 = await browserOutputPath(`${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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nexport const PressKeyToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  key: z.string().describe('Key or key combination (e.g., \"Enter\", \"Tab\", \"Control+a\", \"Meta+Shift+t\", \"ArrowDown\")'),\n  element: ElementSelectorSchema.optional().describe('Optional element to focus before pressing key'),\n  delay: z.number().optional().describe('Delay between keydown and keyup in ms'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class PressKeyTool extends BaseTool<PressKeyToolInput> {\n  static readonly TOOL_NAME = 'browser_press_key';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return PressKeyToolInputSchema;\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: z.toJSONSchema(PressKeyToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const ReloadToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  waitUntil: z\n    .enum(['load', 'domcontentloaded', 'networkidle', 'commit'])\n    .optional()\n    .default('load')\n    .describe('Wait until condition'),\n  timeout: z.number().optional().default(DEFAULT_TOOL_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class ReloadTool extends BaseTool<ReloadToolInput> {\n  static readonly TOOL_NAME = 'browser_reload';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ReloadToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: ReloadTool.TOOL_NAME,\n      description: 'Reloads the current page.',\n      inputSchema: z.toJSONSchema(ReloadToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const ResizePageToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  width: z.number().min(1).describe('Viewport width in pixels'),\n  height: z.number().min(1).describe('Viewport height in pixels'),\n  deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device scale factor (default: 1)'),\n  isMobile: z.boolean().optional().describe('Whether the meta viewport tag is taken into account'),\n  hasTouch: z.boolean().optional().describe('Whether the viewport supports touch events'),\n});\n\nexport class ResizePageTool extends BaseTool<ResizePageToolInput> {\n  static readonly TOOL_NAME = 'browser_resize_page';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ResizePageToolInputSchema;\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: z.toJSONSchema(ResizePageToolInputSchema, { reused: 'inline' }),\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        if (typeof page.setViewportSize !== 'function') {\n          return this.error(`Page \"${input.pageId}\" does not support viewport resizing in the current browser mode`);\n        }\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 = typeof page.viewportSize === 'function' ? page.viewportSize() : undefined;\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_RUN_CODE_TIMEOUT_MS, MAX_TOOL_OUTPUT_CHARS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { ICodeSnippetService, SavedCodeSnippet } from '../services/CodeSnippetService.js';\nimport { ExtensionPageProxy } from '../services/ExtensionPageProxy.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 RunCodeToolInput extends PageTargetInput {\n  /** JavaScript code string containing a function body or 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  /** Execution budget in milliseconds (default 45000) */\n  timeoutMs?: number;\n}\n\nexport const RunCodeToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  code: z\n    .string()\n    .optional()\n    .describe(\n      'JavaScript code to execute. Accepts a function body with page/context/browser in scope or an async function receiving { page, context, browser }. Example body: \"await page.goto(\\'https://example.com\\'); return await page.title();\". Example function: \"async ({ page }) => await page.title()\".',\n    ),\n  snippetPath: z\n    .string()\n    .optional()\n    .describe(\n      'Relative snippet path inside the configured snippets directory. When provided, executes the saved snippet instead of inline code.',\n    ),\n  saveAs: z\n    .object({\n      name: z\n        .string()\n        .describe('Human-readable snippet name. The filename is generated from a sanitized version of this value.'),\n      description: z.string().describe('Short description shown by browser_list_snippets.'),\n    })\n    .optional()\n    .describe('Optional snippet metadata used to save the inline code into the configured snippets directory.'),\n  timeoutMs: z\n    .number()\n    .int()\n    .positive()\n    .optional()\n    .describe(`Execution budget in milliseconds. Defaults to ${DEFAULT_RUN_CODE_TIMEOUT_MS}.`),\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 */\nexport class RunCodeTool extends BaseTool<RunCodeToolInput> {\n  static readonly TOOL_NAME = 'browser_run_code';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly extensionTaskQueue: ExtensionTaskQueue,\n    private readonly snippetService: ICodeSnippetService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return RunCodeToolInputSchema;\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 can be a function body with page/context/browser in scope, or an async function receiving { page, context, browser }. Return a value to include it in the response.',\n      inputSchema: z.toJSONSchema(RunCodeToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  private isFunctionExpressionCandidate(code: string): boolean {\n    const trimmed = code.trim();\n    return (\n      /^(?:async\\s*)?(?:\\(\\s*)?(?:\\{\\s*)?(?:page|context|browser)\\b[\\s\\S]*=>/.test(trimmed) ||\n      /^(?:async\\s+)?function\\s*\\(/.test(trimmed)\n    );\n  }\n\n  private async executeInlineCode(\n    code: string,\n    runtime: { page: unknown; context: unknown; browser: unknown },\n  ): Promise<unknown> {\n    if (this.isFunctionExpressionCandidate(code)) {\n      let maybeRunner: unknown;\n      try {\n        // oxlint-disable-next-line typescript/no-implied-eval -- This tool intentionally executes caller-provided automation code.\n        maybeRunner = (new Function(`return (${code}\\n);`) as () => unknown)();\n      } catch {\n        maybeRunner = undefined;\n      }\n\n      if (typeof maybeRunner === 'function') {\n        return await (maybeRunner as (context: typeof runtime) => unknown)(runtime);\n      }\n    }\n\n    // oxlint-disable-next-line typescript/no-implied-eval -- This tool intentionally executes caller-provided automation code.\n    const runner = new Function('page', 'context', 'browser', `return (async () => { ${code} })();`) as (\n      page: typeof runtime.page,\n      context: typeof runtime.context,\n      browser: typeof runtime.browser,\n    ) => Promise<unknown>;\n    return await runner(runtime.page, runtime.context, runtime.browser);\n  }\n\n  private getSnippetCode(code: string): string {\n    if (!this.isFunctionExpressionCandidate(code)) {\n      return code;\n    }\n\n    return `return await (${code}\\n)({ page, context, browser });`;\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      // Built per call: user code can await for a long time, and a shared proxy\n      // would let a concurrent agent's retarget redirect it mid-run.\n      const extensionPageProxy =\n        pageEntry.mode === 'extension' ? new ExtensionPageProxy(this.extensionTaskQueue) : undefined;\n      extensionPageProxy?.setTarget(pageEntry.id, pageEntry.browserId);\n      const runtimePage = extensionPageProxy ?? page;\n      const runtimeContext = extensionPageProxy ? 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: this.getSnippetCode(input.code as string),\n        });\n      }\n\n      const runtime = {\n        page: runtimePage,\n        context: runtimeContext,\n        browser: runtimeBrowser,\n      };\n\n      // Adapted from playwriter/src/executor.ts:\n      // race user code against a timer so one stuck snippet cannot hold the daemon indefinitely.\n      // ponytail: a synchronous infinite loop still blocks the event loop; upgrade path is\n      // vm.Script with its `timeout` option (playwriter executor.ts runs code that way).\n      const timeoutMs = input.timeoutMs ?? DEFAULT_RUN_CODE_TIMEOUT_MS;\n      let timer: ReturnType<typeof setTimeout> | undefined;\n      const timeout = new Promise<never>((_, reject) => {\n        timer = setTimeout(\n          () =>\n            reject(new Error(`Code execution timed out after ${timeoutMs}ms. Pass timeoutMs to allow longer runs.`)),\n          timeoutMs,\n        );\n      });\n      const run = input.snippetPath\n        ? this.snippetService.loadSnippet(input.snippetPath).then((snippet) => snippet.run(runtime))\n        : this.executeInlineCode(input.code as string, runtime);\n      const result = await Promise.race([run, timeout]).finally(() => clearTimeout(timer));\n\n      if (result === undefined) {\n        if (savedSnippet) {\n          return this.successJson({ savedSnippet });\n        }\n        return this.success('Code executed successfully');\n      }\n\n      const response = this.successJson(savedSnippet ? { result, savedSnippet } : { result });\n      const text = (response.content[0] as { text: string }).text;\n      if (text.length <= MAX_TOOL_OUTPUT_CHARS) {\n        return response;\n      }\n      return this.success(\n        `${text.slice(0, MAX_TOOL_OUTPUT_CHARS)}\\n... [truncated ${text.length - MAX_TOOL_OUTPUT_CHARS} chars; return a smaller value]`,\n      );\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 { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { IAutomationRunner } from '../services/AutomationRunner.js';\nimport { ExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { IExtensionSessionRegistry } from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionSpecExecutionResult, IExtensionSpecRunner } from '../services/ExtensionSpecRunner.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry, PageEntry } from '../services/PageRegistry.js';\nimport type { ISpecBundlerService } from '../services/SpecBundlerService.js';\nimport type { ISpecMetadataService } from '../services/SpecMetadataService.js';\nimport type { TestFilter, Tool, ToolDefinition } from '../types/index.js';\nimport { isOwnedByOtherAgent } from '../utils/ownerContext.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 };\nconst EXTENSION_HEARTBEAT_MAX_AGE_MS = 15_000;\n\ninterface ExtensionTargetPreflight {\n  browserId: string;\n  heartbeatAgeMs: number;\n  pageId: string;\n  remapped: boolean;\n  requestedPageId: string;\n}\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\nexport const RunSpecToolInputSchema = z.object({\n  specPath: z.string().describe('Absolute path to the Playwright spec file to execute'),\n  automationId: z.string().optional().describe('Optional automation session ID (auto-generated if not provided)'),\n  mode: z\n    .enum(['playwright', 'extension'])\n    .optional()\n    .default('playwright')\n    .describe('Execution mode: playwright (standard) or extension (Chrome extension)'),\n  browserId: z.string().optional().describe('Browser ID to use (required for extension mode)'),\n  pageId: z.string().optional().describe('Page ID to use (required for extension mode)'),\n  browserType: z\n    .enum(['chromium', 'firefox', 'webkit'])\n    .optional()\n    .default('chromium')\n    .describe('Browser type to use (only applies to playwright mode)'),\n  headless: z\n    .boolean()\n    .optional()\n    .default(true)\n    .describe('Run browser in headless mode (only applies to playwright mode)'),\n  baseURL: z\n    .string()\n    .optional()\n    .describe('Base URL for relative navigation (e.g., page.goto(\"/\") will use this as the base)'),\n  keepBrowserOpen: z\n    .boolean()\n    .optional()\n    .default(false)\n    .describe('Keep browser open after spec execution completes for further interaction'),\n  hooksPath: z\n    .string()\n    .optional()\n    .describe(\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: z\n    .record(z.string(), z.string())\n    .optional()\n    .describe(\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    ),\n  testName: z.string().optional().describe('Filter by exact test name'),\n  testPattern: z.string().optional().describe('Filter by regex pattern on full test title (e.g., \"login.*success\")'),\n  onlyMarked: z.boolean().optional().default(false).describe('Run only tests marked with test.only()'),\n  describeFilter: z.string().optional().describe('Filter by describe block name pattern'),\n  specArgs: z\n    .record(z.string(), z.unknown())\n    .optional()\n    .describe('Arguments to pass to the spec (available as fixtures.specArgs)'),\n  loadArgsFromEnv: z\n    .boolean()\n    .optional()\n    .default(false)\n    .describe(\"Load specArgs from environment variables using spec's envPrefix export\"),\n  videoDir: z\n    .string()\n    .optional()\n    .describe('Absolute directory path to save video recordings. Supported in both playwright and extension modes.'),\n});\n\nexport class RunSpecTool implements Tool<RunSpecToolInput> {\n  static readonly TOOL_NAME = 'run_spec';\n\n  constructor(\n    private automationRunner: IAutomationRunner,\n    private specMetadataService: ISpecMetadataService,\n    private extensionSpecRunner: IExtensionSpecRunner,\n    private extensionTaskQueue: ExtensionTaskQueue,\n    private specBundlerService: ISpecBundlerService,\n    private pageRegistry?: IPageRegistry,\n    private extensionSessionRegistry?: IExtensionSessionRegistry,\n  ) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return RunSpecToolInputSchema;\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: z.toJSONSchema(RunSpecToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: RunSpecToolInput): Promise<CallToolResult> {\n    const startedAt = Date.now();\n    let hooksDurationMs: number;\n\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 hooksStartedAt = Date.now();\n      const hooksResult = resolvedHooksPath ? await this.runHooks(resolvedHooksPath) : null;\n      hooksDurationMs = Date.now() - hooksStartedAt;\n      if (hooksResult?.baseURL && !input.baseURL) {\n        input.baseURL = hooksResult.baseURL;\n      }\n\n      const mode = input.mode ?? 'playwright';\n      const executionStartedAt = Date.now();\n      let result: CallToolResult;\n\n      if (mode === 'extension') {\n        result = await this.executeExtensionMode(input);\n      } else {\n        const hasEnhancedOptions =\n          input.testName || input.testPattern || input.onlyMarked || input.describeFilter || input.loadArgsFromEnv;\n        result = hasEnhancedOptions\n          ? await this.executeEnhanced(input, resolvedHooksPath)\n          : await this.executeBasic(input, resolvedHooksPath);\n      }\n\n      return this.addExecutionMetadata(result, {\n        executionMs: Date.now() - executionStartedAt,\n        hooksMs: hooksDurationMs,\n        totalMs: Date.now() - startedAt,\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  private addExecutionMetadata(\n    result: CallToolResult,\n    timings: { executionMs: number; hooksMs: number; totalMs: number },\n  ): CallToolResult {\n    const firstContent = result.content[0];\n    let payload: Record<string, unknown> | undefined;\n    if (firstContent?.type === 'text') {\n      try {\n        const parsed = JSON.parse(firstContent.text) as unknown;\n        payload = typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : undefined;\n      } catch {\n        payload = undefined;\n      }\n    }\n\n    const specResult =\n      typeof payload?.specResult === 'object' && payload.specResult !== null\n        ? (payload.specResult as Record<string, unknown>)\n        : payload;\n    const incomplete = specResult?.outcome === 'incomplete' || payload?.status === 'incomplete';\n    const specFailed = specResult?.success === false || payload?.status === 'failed';\n    const category = result.isError\n      ? incomplete\n        ? 'incomplete_run'\n        : specFailed\n          ? 'spec_failure'\n          : 'infrastructure_failure'\n      : 'success';\n\n    return {\n      ...result,\n      structuredContent: {\n        ...(result.structuredContent && typeof result.structuredContent === 'object' ? result.structuredContent : {}),\n        outcome: {\n          category,\n          failedTests: typeof specResult?.failed === 'number' ? specResult.failed : 0,\n          passedTests: typeof specResult?.passed === 'number' ? specResult.passed : 0,\n          timings,\n        },\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    const preflight = this.preflightExtensionTarget(input.pageId, input.browserId);\n    if ('error' in preflight) {\n      return preflight.error;\n    }\n\n    input.pageId = preflight.target.pageId;\n    // Built per call: a spec run is long, and a shared proxy would let another\n    // agent's retarget redirect this run's page mid-spec.\n    const extensionPageProxy = new ExtensionPageProxy(this.extensionTaskQueue);\n    extensionPageProxy.setTarget(preflight.target.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 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        page: extensionPageProxy,\n      });\n    } finally {\n      if (shouldRecord && input.videoDir) {\n        try {\n          const videoBase64 = await 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, preflight.target);\n  }\n\n  private preflightExtensionTarget(\n    requestedPageId: string,\n    browserId: string,\n  ): { target: ExtensionTargetPreflight } | { error: CallToolResult } {\n    if (!this.pageRegistry || !this.extensionSessionRegistry) {\n      return {\n        error: this.extensionPreflightError(\n          'BROWSER_EXTENSION_PREFLIGHT_UNAVAILABLE',\n          'Extension target registries are unavailable.',\n          requestedPageId,\n          browserId,\n        ),\n      };\n    }\n\n    const session = this.extensionSessionRegistry.getSessionByBrowserId(browserId);\n    if (!session) {\n      return {\n        error: this.extensionPreflightError(\n          'BROWSER_EXTENSION_SESSION_MISSING',\n          `No live extension session is registered for browser ${browserId}.`,\n          requestedPageId,\n          browserId,\n        ),\n      };\n    }\n\n    const heartbeatAgeMs = Math.max(0, Date.now() - session.lastHeartbeatAt.getTime());\n    if (heartbeatAgeMs > EXTENSION_HEARTBEAT_MAX_AGE_MS) {\n      return {\n        error: this.extensionPreflightError(\n          'BROWSER_EXTENSION_SESSION_STALE',\n          `Extension session heartbeat is stale by ${heartbeatAgeMs}ms.`,\n          requestedPageId,\n          browserId,\n          heartbeatAgeMs,\n        ),\n      };\n    }\n\n    const requestedPage = this.pageRegistry.get(requestedPageId);\n    if (requestedPage && isOwnedByOtherAgent(requestedPage)) {\n      return {\n        error: this.extensionPreflightError(\n          'BROWSER_EXTENSION_PAGE_OWNERSHIP_MISMATCH',\n          `Page ${requestedPageId} belongs to another agent session.`,\n          requestedPageId,\n          browserId,\n          heartbeatAgeMs,\n        ),\n      };\n    }\n\n    const requestedPageIsValid = requestedPage?.mode === 'extension' && requestedPage.browserId === browserId;\n    if (requestedPageIsValid) {\n      return {\n        target: {\n          browserId,\n          heartbeatAgeMs,\n          pageId: requestedPageId,\n          remapped: false,\n          requestedPageId,\n        },\n      };\n    }\n\n    const recoveryCandidates = this.pageRegistry\n      .findByBrowser(browserId)\n      .filter((page): page is PageEntry => page.mode === 'extension' && !isOwnedByOtherAgent(page));\n    if (recoveryCandidates.length !== 1) {\n      return {\n        error: this.extensionPreflightError(\n          'BROWSER_EXTENSION_PAGE_UNRESOLVED',\n          `Page ${requestedPageId} is stale or does not belong to browser ${browserId}, and exactly one replacement page could not be resolved.`,\n          requestedPageId,\n          browserId,\n          heartbeatAgeMs,\n        ),\n      };\n    }\n\n    return {\n      target: {\n        browserId,\n        heartbeatAgeMs,\n        pageId: recoveryCandidates[0].id,\n        remapped: true,\n        requestedPageId,\n      },\n    };\n  }\n\n  private extensionPreflightError(\n    code: string,\n    reason: string,\n    pageId: string,\n    browserId: string,\n    heartbeatAgeMs?: number,\n  ): CallToolResult {\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(\n            {\n              status: 'incomplete',\n              code,\n              reason,\n              browserId,\n              pageId,\n              heartbeatAgeMs,\n            },\n            null,\n            2,\n          ),\n        },\n      ],\n      isError: true,\n    };\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 =\n      result.status === 'completed'\n        ? keepBrowserOpen\n          ? 'Spec completed. Browser kept open for further interaction.'\n          : 'Spec completed.'\n        : keepBrowserOpen\n          ? 'Spec failed. Browser kept open for further interaction.'\n          : 'Spec failed.';\n    const isError = result.status !== 'completed' || result.specResult?.success === false;\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              warnings: result.specResult?.warnings,\n              message,\n            },\n            null,\n            2,\n          ),\n        },\n      ],\n      ...(isError ? { isError: true } : {}),\n    };\n  }\n\n  private formatExtensionResult(\n    result: ExtensionSpecExecutionResult,\n    preflight: ExtensionTargetPreflight,\n  ): CallToolResult {\n    const message =\n      result.outcome === 'incomplete'\n        ? `Spec incomplete: ${result.incompleteReason}`\n        : 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.outcome,\n              discoveredTests: result.discoveredTests,\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              warnings: result.warnings,\n              incompleteReason: result.incompleteReason,\n              preflight,\n              message,\n            },\n            null,\n            2,\n          ),\n        },\n      ],\n      isError: !result.success,\n    };\n  }\n}\n","import type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport { ExtensionPageProxy } from '../services/ExtensionPageProxy.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.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\nexport const SaveProfileStateToolInputSchema = z.object({\n  profileName: z.string().describe('Profile to save state into'),\n  browserId: z.string().optional().describe('Browser to read state from'),\n});\n\nexport class SaveProfileStateTool implements Tool<SaveProfileStateToolInput> {\n  static readonly TOOL_NAME = 'browser_save_profile_state';\n\n  constructor(\n    private readonly profileService: IProfileService,\n    private readonly browserService: IBrowserService,\n    private readonly extensionTaskQueue: ExtensionTaskQueue,\n  ) {}\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return SaveProfileStateToolInputSchema;\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: z.toJSONSchema(SaveProfileStateToolInputSchema, { reused: 'inline' }),\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        // Built per call: a shared proxy carries one target, so a concurrent\n        // agent could retarget it mid-await and we would save its cookies here.\n        const extensionPageProxy = new ExtensionPageProxy(this.extensionTaskQueue);\n        extensionPageProxy.setTarget(pageId, browser.id);\n        const storageState = await 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 { randomUUID } from 'node:crypto';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { browserOutputPath } from '../utils/ownerContext.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  /** Explicit output path */\n  path?: string;\n}\n\nexport const ScreenshotToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  fullPage: z.boolean().optional().default(false).describe('Capture full page scrollable area'),\n  selector: z.string().optional().describe('CSS selector for element to screenshot'),\n  clip: z\n    .object({\n      x: z.number().min(0).describe('X coordinate of top-left corner'),\n      y: z.number().min(0).describe('Y coordinate of top-left corner'),\n      width: z.number().min(1).describe('Width of clipping region'),\n      height: z.number().min(1).describe('Height of clipping region'),\n    })\n    .optional()\n    .describe('Clip region to capture by coordinates (cannot be used with selector)'),\n  type: z.enum(['png', 'jpeg']).optional().default('png').describe('Image format'),\n  quality: z.number().min(0).max(100).optional().describe('Quality for jpeg format (0-100)'),\n  omitBackground: z.boolean().optional().default(false).describe('Omit background for transparent PNG'),\n  filename: z.string().optional().describe('Custom filename without extension'),\n  path: z.string().optional().describe('Explicit output path for the screenshot'),\n});\n\nexport class ScreenshotTool extends BaseTool<ScreenshotToolInput> {\n  static readonly TOOL_NAME = 'browser_screenshot';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return ScreenshotToolInputSchema;\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: z.toJSONSchema(ScreenshotToolInputSchema, { reused: 'inline' }),\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 = input.path?.trim() ? input.path : await browserOutputPath(`${filename}.${imageType}`);\n\n        if (input.selector) {\n          if (typeof page.$ !== 'function') {\n            return this.error(`Page \"${input.pageId}\" does not support selector screenshots`);\n          }\n          const element = await page.$(input.selector);\n          if (!element) {\n            throw new Error(`Element not found: ${input.selector}`);\n          }\n          if (typeof element.screenshot !== 'function') {\n            return this.error(`Element \"${input.selector}\" does not support screenshots`);\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          if (typeof page.screenshot !== 'function') {\n            return this.error(`Page \"${input.pageId}\" does not support screenshots in the current browser mode`);\n          }\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { assertPageOwnership } from '../utils/ownerContext.js';\nimport { BaseTool } from './BaseTool.js';\n\nexport interface SelectPageToolInput {\n  /** Page ID to set as current */\n  pageId: string;\n}\n\nexport const SelectPageToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to set as current'),\n});\n\nexport class SelectPageTool extends BaseTool<SelectPageToolInput> {\n  static readonly TOOL_NAME = 'browser_select_page';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return SelectPageToolInputSchema;\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: z.toJSONSchema(SelectPageToolInputSchema, { reused: 'inline' }),\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      assertPageOwnership(pageEntry, SelectPageTool.TOOL_NAME);\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nexport const SelectToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  values: z.array(z.string()).optional().describe('Option value(s) to select'),\n  labels: z.array(z.string()).optional().describe('Option label(s) to select'),\n  indexes: z.array(z.number()).optional().describe('Option index(es) to select'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class SelectTool extends BaseTool<SelectToolInput> {\n  static readonly TOOL_NAME = 'browser_select';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return SelectToolInputSchema;\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: z.toJSONSchema(SelectToolInputSchema, { reused: 'inline' }),\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 * Snapshot text helpers: smart diff against the previous snapshot and line search.\n *\n * Adapted from playwriter: playwriter/src/diff-utils.ts and playwriter/src/executor.ts.\n */\n\nimport { structuredPatch } from 'diff';\n\nexport interface SmartDiffResult {\n  type: 'diff' | 'full' | 'no-change';\n  content: string;\n}\n\n/**\n * Diff `newContent` against `oldContent`. Returns the full new content when more than\n * `threshold` of the lines changed or the diff would be longer than the content itself.\n */\nexport function createSmartDiff(oldContent: string, newContent: string, threshold = 0.5): SmartDiffResult {\n  const patch = structuredPatch('snapshot', 'snapshot', oldContent, newContent, 'previous', 'current', {\n    context: 3,\n  });\n\n  let addedLines = 0;\n  let removedLines = 0;\n  for (const hunk of patch.hunks) {\n    for (const line of hunk.lines) {\n      if (line.startsWith('+')) addedLines++;\n      else if (line.startsWith('-')) removedLines++;\n    }\n  }\n\n  if (addedLines === 0 && removedLines === 0) {\n    return { type: 'no-change', content: newContent };\n  }\n\n  // A replacement counts as both an add and a remove, so use the max to keep the ratio in 0..1.\n  const maxLines = Math.max(oldContent.split('\\n').length, newContent.split('\\n').length, 1);\n  const changeRatio = Math.min(Math.max(addedLines, removedLines) / maxLines, 1);\n\n  const diffLines: string[] = ['--- snapshot (previous)', '+++ snapshot (current)'];\n  for (const hunk of patch.hunks) {\n    diffLines.push(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`, ...hunk.lines);\n  }\n  const diffString = diffLines.join('\\n');\n\n  if (changeRatio >= threshold || diffString.length >= newContent.length) {\n    return { type: 'full', content: newContent };\n  }\n  return { type: 'diff', content: diffString };\n}\n\nconst MAX_SEARCH_MATCHES = 10;\nconst SEARCH_CONTEXT_LINES = 5;\n\n/** Return up to 10 lines containing `search` (case-insensitive), each with 5 lines of context. */\nexport function searchSnapshot(snapshot: string, search: string): string {\n  const lines = snapshot.split('\\n');\n  const needle = search.toLowerCase();\n  const matchIndices: number[] = [];\n  for (let i = 0; i < lines.length && matchIndices.length < MAX_SEARCH_MATCHES; i++) {\n    if (lines[i]?.toLowerCase().includes(needle)) matchIndices.push(i);\n  }\n  if (matchIndices.length === 0) {\n    return `No matches found for \"${search}\"`;\n  }\n\n  const included = new Set<number>();\n  for (const idx of matchIndices) {\n    const end = Math.min(lines.length - 1, idx + SEARCH_CONTEXT_LINES);\n    for (let i = Math.max(0, idx - SEARCH_CONTEXT_LINES); i <= end; i++) included.add(i);\n  }\n\n  const sorted = [...included].sort((a, b) => a - b);\n  const result: string[] = [];\n  sorted.forEach((lineIdx, i) => {\n    if (i > 0 && sorted[i - 1] !== lineIdx - 1) result.push('---');\n    result.push(lines[lineIdx] ?? '');\n  });\n  return result.join('\\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 { randomUUID } from 'node:crypto';\nimport { writeFile } from 'node:fs/promises';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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 { browserOutputPath } from '../utils/ownerContext.js';\nimport { createSmartDiff, searchSnapshot } from '../utils/snapshotText.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\n/** Pages whose last snapshot is kept for diffing; oldest entries are evicted first. */\nconst MAX_BASELINES = 50;\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  /** Return the full snapshot instead of a diff against the previous one */\n  fresh?: boolean;\n  /** Return only lines containing this text (up to 10 matches with 5 lines of context) */\n  search?: string;\n}\n\nexport const SnapshotToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  root: z.string().optional().describe('CSS selector for root element to snapshot (defaults to body)'),\n  sizeThreshold: z\n    .number()\n    .optional()\n    .default(10000)\n    .describe('Size threshold in bytes; snapshots larger than this are saved to file (default: 10000)'),\n  fresh: z\n    .boolean()\n    .optional()\n    .describe(\n      'Return the full snapshot. By default a repeat snapshot of the same page and URL returns only the diff since the previous one.',\n    ),\n  search: z\n    .string()\n    .optional()\n    .describe('Return only lines containing this text (case-insensitive, up to 10 matches with 5 lines of context).'),\n});\n\nexport class SnapshotTool extends BaseTool<SnapshotToolInput> {\n  static readonly TOOL_NAME = 'browser_snapshot';\n\n  /** Last full snapshot per page and root, used as the diff baseline. Insertion order doubles as LRU order. */\n  private readonly baselines = new Map<string, { url: string; snapshot: string }>();\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return SnapshotToolInputSchema;\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. Repeat snapshots of the same URL return a diff since the previous snapshot; pass fresh for the full tree or search to find specific lines.',\n      inputSchema: z.toJSONSchema(SnapshotToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: SnapshotToolInput): Promise<CallToolResult> {\n    // Both modes return `URL: <url>\\n<snapshot lines>`; diff, search and the\n    // file-save threshold are applied here so Playwright and extension pages behave the same.\n    const raw = await 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        const snapshot = await this.captureSnapshot(page, input.root);\n        if (!snapshot) {\n          return this.success('No accessibility tree available for this page');\n        }\n        return this.success(`URL: ${page.url()}\\n${snapshot}`);\n      },\n    );\n\n    const rawText = raw.isError ? undefined : (raw.content[0] as { text?: string } | undefined)?.text;\n    const match = rawText?.match(/^URL: (.*)\\n([\\s\\S]*)$/);\n    if (!match) {\n      return raw;\n    }\n    const [, url = '', snapshot = ''] = match;\n\n    return this.safeExecute(async () => {\n      const body = input.search\n        ? searchSnapshot(snapshot, input.search)\n        : this.diffAgainstBaseline(`${input.pageId}|${input.root ?? ''}`, url, snapshot, input.fresh);\n      const text = `URL: ${url}\\n${body}`;\n\n      const sizeThreshold = input.sizeThreshold ?? DEFAULT_SIZE_THRESHOLD;\n      if (Buffer.byteLength(text, 'utf-8') <= sizeThreshold) {\n        return this.success(text);\n      }\n\n      // Save large snapshot to temp file\n      const filePath = await browserOutputPath(`snapshot-${randomUUID()}.yaml`);\n      await writeFile(filePath, text, 'utf-8');\n      return this.success(`Snapshot saved to: ${filePath}`);\n    });\n  }\n\n  /**\n   * Records `snapshot` as the new baseline and returns what the agent needs to see:\n   * the full text on first sight, a URL change, or `fresh`; otherwise a diff or a no-change line.\n   * Diff rule adapted from playwriter/src/executor.ts.\n   */\n  private diffAgainstBaseline(key: string, url: string, snapshot: string, fresh?: boolean): string {\n    const previous = this.baselines.get(key);\n    this.baselines.delete(key);\n    this.baselines.set(key, { url, snapshot });\n    if (this.baselines.size > MAX_BASELINES) {\n      const oldest = this.baselines.keys().next().value;\n      if (oldest !== undefined) this.baselines.delete(oldest);\n    }\n\n    if (fresh || !previous || previous.url !== url) {\n      return snapshot;\n    }\n\n    const diff = createSmartDiff(previous.snapshot, snapshot);\n    if (diff.type === 'no-change') {\n      return 'No changes since the previous snapshot. Pass fresh: true for the full tree.';\n    }\n    if (diff.type === 'diff') {\n      return `Diff since the previous snapshot (pass fresh: true for the full tree):\\n${diff.content}`;\n    }\n    return snapshot;\n  }\n  private async captureSnapshot(page: import('playwright').Page, requestedRoot?: string): Promise<string | null> {\n    const selectors = requestedRoot ? [requestedRoot] : ['body', 'html'];\n\n    for (const selector of selectors) {\n      const locator = page.locator(selector).first();\n      const count = await locator.count();\n      if (count === 0) {\n        continue;\n      }\n\n      try {\n        return await locator.ariaSnapshot();\n      } catch {\n        // Try next fallback selector when the current root is transiently unavailable.\n      }\n    }\n\n    if (!requestedRoot) {\n      await page.waitForTimeout(250);\n      for (const selector of ['body', 'html']) {\n        const locator = page.locator(selector).first();\n        const count = await locator.count();\n        if (count === 0) {\n          continue;\n        }\n        try {\n          return await locator.ariaSnapshot();\n        } catch {\n          // Exhaust fallbacks before failing.\n        }\n      }\n    }\n\n    throw new Error(`Root element not found: ${requestedRoot ?? 'body/html'}`);\n  }\n}\n","import path from 'node:path';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const StartRecordingToolInputSchema = z.object({\n  pageId: z.string().describe('The page ID to record.'),\n  path: z\n    .string()\n    .optional()\n    .describe(\n      'Optional absolute output path for the .webm file. If omitted, browse-tool creates a temporary recording path.',\n    ),\n});\n\nexport class StartRecordingTool extends BaseTool<StartRecordingToolInput> {\n  static readonly TOOL_NAME = 'browser_start_recording';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return StartRecordingToolInputSchema;\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: z.toJSONSchema(StartRecordingToolInputSchema, { reused: 'inline' }),\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 * 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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const StartTraceToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  name: z.string().optional().describe('Name for the trace (used in trace filename)'),\n  screenshots: z.boolean().optional().describe('Whether to capture screenshots during tracing (default: true)'),\n  snapshots: z.boolean().optional().describe('Whether to capture snapshots during tracing (default: true)'),\n  sources: z.boolean().optional().describe('Whether to include source files in the trace (default: false)'),\n});\n\nexport class StartTraceTool extends BaseTool<StartTraceToolInput> {\n  static readonly TOOL_NAME = 'browser_start_trace';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return StartTraceToolInputSchema;\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: z.toJSONSchema(StartTraceToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const StopRecordingToolInputSchema = z.object({\n  pageId: z.string().describe('The page ID whose recording should be stopped.'),\n  includeBase64: z.boolean().optional().describe('Whether to include the base64-encoded WebM payload in the response.'),\n});\n\nexport class StopRecordingTool extends BaseTool<StopRecordingToolInput> {\n  static readonly TOOL_NAME = 'browser_stop_recording';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return StopRecordingToolInputSchema;\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: z.toJSONSchema(StopRecordingToolInputSchema, { reused: 'inline' }),\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 * 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 path from 'node:path';\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\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\nexport const StopTraceToolInputSchema = z.object({\n  pageId: z.string().describe('Optional page ID to override browser current page'),\n  path: z\n    .string()\n    .describe(\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\nexport class StopTraceTool extends BaseTool<StopTraceToolInput> {\n  static readonly TOOL_NAME = 'browser_stop_trace';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return StopTraceToolInputSchema;\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: z.toJSONSchema(StopTraceToolInputSchema, { reused: 'inline' }),\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","/**\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nexport const TypeToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  text: z.string().describe('The text to type character by character'),\n  delay: z.number().optional().default(0).describe('Delay between key presses in ms'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class TypeTool extends BaseTool<TypeToolInput> {\n  static readonly TOOL_NAME = 'browser_type';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return TypeToolInputSchema;\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: z.toJSONSchema(TypeToolInputSchema, { reused: 'inline' }),\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        // `text` is the string to type here, so it cannot double as a visible-text locator.\n        const locator = await this.elementLocator.locate(page, {\n          uid: input.uid,\n          role: input.role,\n          name: input.name,\n          label: input.label,\n          placeholder: input.placeholder,\n          testId: input.testId,\n          exact: input.exact,\n          selector: input.selector,\n          xpath: input.xpath,\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_ACTION_TIMEOUT_MS } from '../constants/timeouts.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport {\n  type ElementSelector,\n  ElementSelectorSchema,\n  type IElementLocatorService,\n} 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\nexport const UploadFileToolInputSchema = ElementSelectorSchema.extend({\n  pageId: z.string().describe('Page ID to operate on'),\n  files: z.union([z.string(), z.array(z.string())]).describe('File path(s) to upload'),\n  timeout: z.number().optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class UploadFileTool extends BaseTool<UploadFileToolInput> {\n  static readonly TOOL_NAME = 'browser_upload_file';\n\n  constructor(\n    pageRegistry: IPageRegistry,\n    browserService: IBrowserService,\n    toolExecutor: ToolExecutor,\n    private readonly elementLocator: IElementLocatorService,\n  ) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return UploadFileToolInputSchema;\n  }\n\n  getDefinition(): ToolDefinition {\n    return {\n      name: UploadFileTool.TOOL_NAME,\n      description: 'Uploads file(s) to a file input element.',\n      inputSchema: z.toJSONSchema(UploadFileToolInputSchema, { reused: 'inline' }),\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 type { CallToolResult } from '@modelcontextprotocol/server';\nimport { z } from 'zod';\n\nimport { DEFAULT_TOOL_TIMEOUT_MS } from '../constants/timeouts.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\nexport const WaitForToolInputSchema = z.object({\n  pageId: z.string().describe('Page ID to operate on'),\n  type: z.enum(['element', 'text', 'timeout', 'loadState']).optional().describe('Type of wait condition'),\n  selector: z.string().optional().describe('CSS selector for element wait'),\n  text: z.string().optional().describe('Text to wait for (supports regex patterns)'),\n  duration: z.number().optional().describe('Duration in milliseconds for timeout wait'),\n  state: z.enum(['load', 'domcontentloaded', 'networkidle']).optional().describe('Load state to wait for'),\n  elementState: z\n    .enum(['attached', 'detached', 'visible', 'hidden'])\n    .optional()\n    .default('visible')\n    .describe('Element state to wait for'),\n  timeout: z.number().optional().default(DEFAULT_TOOL_TIMEOUT_MS).describe('Timeout in milliseconds'),\n});\n\nexport class WaitForTool extends BaseTool<WaitForToolInput> {\n  static readonly TOOL_NAME = 'browser_wait_for';\n\n  constructor(pageRegistry: IPageRegistry, browserService: IBrowserService, toolExecutor: ToolExecutor) {\n    super(pageRegistry, browserService, toolExecutor);\n  }\n\n  getInputSchema(): z.ZodObject<z.ZodRawShape> {\n    return WaitForToolInputSchema;\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: z.toJSONSchema(WaitForToolInputSchema, { reused: 'inline' }),\n    };\n  }\n\n  async execute(input: WaitForToolInput): Promise<CallToolResult> {\n    const waitType =\n      input.type ??\n      (input.text\n        ? 'text'\n        : input.selector\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    if (waitType === 'element' && !input.selector) {\n      return this.error('selector is required for element wait');\n    }\n\n    if (waitType === 'text' && !input.text) {\n      return this.error('text is required for text wait');\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: ${JSON.stringify(waitType)}`);\n        }\n      },\n    );\n  }\n}\n","/**\n * Browser runtime composition\n *\n * `createBrowseRuntime` wires every service and tool by hand from one\n * `BrowseOptions` object. It reads no environment variables and registers no\n * `process` listeners, so a host can create and dispose it in-process.\n * `browseOptionsFromEnv` maps the legacy environment variables for the\n * command-line entry points only.\n */\n\nimport fs from 'node:fs';\n\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport { ProcessRegistryService } from '@agimon-ai/foundation-process-registry';\nimport { globalPoConfigPath, parsePoConfig } from '@agimon-ai/po-config/config';\n\nimport { ExtensionLinkTokens } from '../server/extensionLinkTokens.js';\nimport { AutomationRunner } from '../services/AutomationRunner.js';\nimport { BrowserLockManager } from '../services/BrowserLockManager.js';\nimport { BrowserProcessRegistry } from '../services/BrowserProcessRegistry.js';\nimport { BrowserService } from '../services/BrowserService.js';\nimport {\n  CHROME_FOR_TESTING_PATH_ENV_VAR,\n  CHROME_FOR_TESTING_VERSION_ENV_VAR,\n  ChromeForTestingService,\n} 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 {\n  BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR,\n  IdleCleanupService,\n  resolveBrowserIdleTimeoutMs,\n  resolveServerIdleTimeoutMs,\n  SERVER_IDLE_TIMEOUT_MINUTES_ENV_VAR,\n} from '../services/IdleCleanupService.js';\nimport { PageMonitorService } from '../services/PageMonitorService.js';\nimport { PageRegistry } from '../services/PageRegistry.js';\nimport { ProfileService } from '../services/ProfileService.js';\nimport { PROXY_CONFIG_DIR_ENV_VAR, ProxyConfigService } from '../services/ProxyConfigService.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 { InspectElementTool } from '../tools/InspectElementTool.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 { ResolveLocatorTool } from '../tools/ResolveLocatorTool.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 { StartRecordingTool } from '../tools/StartRecordingTool.js';\nimport { StartTraceTool } from '../tools/StartTraceTool.js';\nimport { StopRecordingTool } from '../tools/StopRecordingTool.js';\nimport { StopTraceTool } from '../tools/StopTraceTool.js';\nimport { TypeTool } from '../tools/TypeTool.js';\nimport { UploadFileTool } from '../tools/UploadFileTool.js';\nimport { WaitForTool } from '../tools/WaitForTool.js';\nimport type { BrowseOptions, BrowseSettings, Tool } from '../types/index.js';\nimport { resolveWorkspaceRoot } from '../utils/workspaceRoot.js';\n\ntype Env = Readonly<Record<string, string | undefined>>;\n\n/** The services and tools of one browser runtime. */\nexport interface BrowseRuntime {\n  readonly browserService: BrowserService;\n  readonly pageRegistry: PageRegistry;\n  readonly profileService: ProfileService;\n  readonly stealthLauncher: StealthLauncher;\n  readonly extensionTaskQueue: ExtensionTaskQueue;\n  readonly extensionSessionRegistry: ExtensionSessionRegistry;\n  readonly webSocketHub: WebSocketHub;\n  readonly telemetry: TelemetryService;\n  readonly idleCleanup: IdleCleanupService;\n  readonly processRegistry: ProcessRegistryService;\n  readonly browserProcessRegistry: BrowserProcessRegistry;\n  readonly webServerManager: WebServerManager;\n  /** Per-launch tokens the Chrome extension presents when `serverOrigin` is set. */\n  readonly linkTokens: ExtensionLinkTokens;\n  /** Every tool, keyed by its tool name (`browser_launch`, ...). */\n  readonly tools: Readonly<Record<string, Tool>>;\n  /** Stops timers and closes every browser and dev server. Safe to call twice. */\n  dispose(): Promise<void>;\n}\n\nexport function createBrowseRuntime(options: BrowseOptions = {}): BrowseRuntime {\n  const workspaceRoot = options.workspaceRoot ?? resolveWorkspaceRoot({ env: {} });\n  const environment = options.registry?.environment || 'development';\n  const env = options.env ?? {};\n\n  const telemetry = new TelemetryService({ env, workspaceRoot });\n  const webSocketHub = new WebSocketHub();\n  const linkTokens = new ExtensionLinkTokens();\n  const profileService = new ProfileService({ profilesDirectory: options.profilesDirectory });\n  const pageRegistry = new PageRegistry(webSocketHub);\n  const lockManager = new BrowserLockManager();\n  const chromeForTesting = new ChromeForTestingService({\n    executablePath: options.chrome?.chromeForTestingPath,\n    version: options.chrome?.chromeForTestingVersion,\n  });\n  const pageMonitor = new PageMonitorService();\n  const extensionSessionRegistry = new ExtensionSessionRegistry();\n  const extensionTaskQueue = new ExtensionTaskQueue(webSocketHub, telemetry, pageRegistry);\n  const processRegistry = new ProcessRegistryService(\n    options.registry?.processRegistryPath,\n    undefined,\n    new PortRegistryService(options.registry?.portRegistryPath),\n  );\n  const browserProcessRegistry = new BrowserProcessRegistry(processRegistry, {\n    repositoryPath: workspaceRoot,\n    environment,\n  });\n  const browserService = new BrowserService(\n    profileService,\n    pageRegistry,\n    lockManager,\n    chromeForTesting,\n    pageMonitor,\n    webSocketHub,\n    extensionSessionRegistry,\n    extensionTaskQueue,\n    browserProcessRegistry,\n    { ...options, workspaceRoot },\n  );\n  const stealthLauncher = new StealthLauncher(profileService, pageRegistry, lockManager, {\n    extensionPath: options.extensionPath,\n  });\n  const idleCleanup = new IdleCleanupService(browserService, pageRegistry, stealthLauncher, {\n    browserIdleMs: options.timeouts?.browserIdleMs,\n    serverIdleMs: options.timeouts?.serverIdleMs,\n  });\n  const proxyConfigService = new ProxyConfigService({\n    env,\n    profilesDir: profileService.getProfilesDir(),\n    proxyConfigDir: options.proxyConfigDirectory,\n  });\n  const delegator = new ExtensionToolDelegator(extensionTaskQueue);\n  const toolExecutor = new ToolExecutor(pageRegistry, browserService, delegator, telemetry);\n  const elementLocator = new ElementLocatorService();\n  const snippetService = new CodeSnippetService(options.snippetsDirectory);\n  const specBundler = new SpecBundlerService();\n  const specMetadata = new SpecMetadataService(specBundler);\n  const webServerManager = new WebServerManager();\n  const automationRunner = new AutomationRunner(\n    browserService,\n    new SpecRunner(specBundler),\n    specMetadata,\n    new SetupRunner(specBundler),\n    webServerManager,\n    pageRegistry,\n  );\n  const extensionSpecRunner = new ExtensionSpecRunner(\n    specBundler,\n    new ExtensionPageProxy(extensionTaskQueue),\n    extensionSessionRegistry,\n  );\n\n  const page = [pageRegistry, browserService, toolExecutor] as const;\n  const toolList: Tool[] = [\n    new ClickTool(...page, elementLocator),\n    new FillTool(...page, elementLocator),\n    new TypeTool(...page, elementLocator),\n    new SelectTool(...page, elementLocator),\n    new HoverTool(...page, elementLocator),\n    new DragTool(...page, elementLocator),\n    new PressKeyTool(...page, elementLocator),\n    new InspectElementTool(...page, elementLocator),\n    new UploadFileTool(...page, elementLocator),\n    new NavigateTool(...page),\n    new GoBackTool(...page),\n    new GoForwardTool(...page),\n    new ReloadTool(...page),\n    new ResolveLocatorTool(...page),\n    new WaitForTool(...page),\n    new SnapshotTool(...page),\n    new ScreenshotTool(...page),\n    new PdfTool(...page, extensionTaskQueue),\n    new ListPagesTool(...page),\n    new NewPageTool(...page),\n    new SelectPageTool(...page),\n    new ClosePageTool(...page),\n    new HandleDialogTool(...page),\n    new ListNetworkRequestsTool(...page, pageMonitor),\n    new GetNetworkRequestTool(...page, pageMonitor),\n    new ListConsoleMessagesTool(...page, pageMonitor),\n    new EvaluateScriptTool(...page),\n    new EmulateTool(...page),\n    new ResizePageTool(...page),\n    new ExpectTool(...page, extensionTaskQueue),\n    new StartRecordingTool(...page),\n    new StopRecordingTool(...page),\n    new StartTraceTool(...page),\n    new StopTraceTool(...page),\n    new ListProfilesTool(profileService),\n    new ListSnippetsTool(snippetService),\n    new CreateProfileTool(profileService),\n    new DeleteProfileTool(profileService),\n    new SaveProfileStateTool(profileService, browserService, extensionTaskQueue),\n    new RunSpecTool(\n      automationRunner,\n      specMetadata,\n      extensionSpecRunner,\n      extensionTaskQueue,\n      specBundler,\n      pageRegistry,\n      extensionSessionRegistry,\n    ),\n    new DiscoverSpecsTool(new SpecDiscoveryService()),\n    new LaunchBrowserTool(\n      browserService,\n      stealthLauncher,\n      proxyConfigService,\n      { ...options, workspaceRoot },\n      linkTokens,\n    ),\n    new CloseBrowserTool(...page),\n    new RunCodeTool(...page, extensionTaskQueue, snippetService),\n  ];\n  const tools = Object.freeze(Object.fromEntries(toolList.map((tool) => [tool.getDefinition().name, tool])));\n\n  let disposed: Promise<void> | undefined;\n  const dispose = (): Promise<void> => {\n    disposed ??= (async () => {\n      idleCleanup.stop();\n      linkTokens.revokeAll();\n      webSocketHub.stopLivenessSweep();\n      const results = await Promise.allSettled([\n        browserService.closeAll(),\n        stealthLauncher.closeAll(),\n        webServerManager.stopServer(),\n      ]);\n      for (const result of results) {\n        if (result.status === 'rejected') console.error('[BrowseRuntime] Dispose step failed:', result.reason);\n      }\n      await telemetry.shutdown();\n    })();\n    return disposed;\n  };\n\n  return {\n    browserService,\n    pageRegistry,\n    profileService,\n    stealthLauncher,\n    extensionTaskQueue,\n    extensionSessionRegistry,\n    webSocketHub,\n    telemetry,\n    idleCleanup,\n    processRegistry,\n    browserProcessRegistry,\n    webServerManager,\n    linkTokens,\n    tools,\n    dispose,\n  };\n}\n\n/**\n * The engine and fallback from Po's global `browse` settings. `BROWSE_TOOL_ENGINE`\n * wins so one run can override them.\n */\nexport function readBrowseSettings(env: Env): BrowseSettings {\n  const configPath = globalPoConfigPath();\n  const browse = fs.existsSync(configPath)\n    ? parsePoConfig(fs.readFileSync(configPath, 'utf8'), configPath, { lenient: true }).config.browse\n    : undefined;\n  const fromEnv = env.BROWSE_TOOL_ENGINE;\n  const engine = fromEnv === 'cloak' || fromEnv === 'chrome-for-testing' ? fromEnv : browse?.engine;\n  return { engine, fallback: browse?.fallback === 'chrome-for-testing' };\n}\n\nfunction positiveInt(raw: string | undefined): number | undefined {\n  const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;\n  return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction trimmed(raw: string | undefined): string | undefined {\n  return raw?.trim() || undefined;\n}\n\n/** The legacy environment variables as `BrowseOptions`, for the command-line entry points only. */\nexport function browseOptionsFromEnv(env: Env): BrowseOptions {\n  const port = positiveInt(env.PLAYWRIGHT_PORT);\n  return {\n    profilesDirectory: env.PLAYWRIGHT_PROFILES_DIR || undefined,\n    proxyConfigDirectory: env[PROXY_CONFIG_DIR_ENV_VAR] || undefined,\n    snippetsDirectory: env.BROWSE_TOOL_SNIPPETS_DIR || undefined,\n    workspaceRoot: resolveWorkspaceRoot({ env }),\n    settings: () => readBrowseSettings(env),\n    env,\n    chrome: {\n      executablePath: env.CHROME_BIN || env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined,\n      chromeForTestingPath: trimmed(env[CHROME_FOR_TESTING_PATH_ENV_VAR]),\n      chromeForTestingVersion: trimmed(env[CHROME_FOR_TESTING_VERSION_ENV_VAR]),\n      disableGpu: env.BROWSE_TOOL_DISABLE_GPU === 'true',\n      autoSelectTabCaptureSourceTitle: trimmed(env.BROWSE_TOOL_AUTO_SELECT_TAB_CAPTURE_SOURCE_TITLE),\n      autoSelectDesktopCaptureSource: trimmed(env.BROWSE_TOOL_AUTO_SELECT_DESKTOP_CAPTURE_SOURCE),\n      useFakeUiForMediaStream: env.BROWSE_TOOL_USE_FAKE_UI_FOR_MEDIA_STREAM === '1',\n    },\n    timeouts: {\n      profileLaunchMs: positiveInt(env.BROWSE_TOOL_PROFILE_LAUNCH_TIMEOUT_MS),\n      browserIdleMs: resolveBrowserIdleTimeoutMs(env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR]),\n      serverIdleMs: resolveServerIdleTimeoutMs(env[SERVER_IDLE_TIMEOUT_MINUTES_ENV_VAR]),\n    },\n    vm: {\n      serverUrl: env.PLAYWRIGHT_VM_SERVER_URL || undefined,\n      extensionPath: env.PLAYWRIGHT_VM_EXTENSION_PATH || undefined,\n      docker: {\n        browser: env.PLAYWRIGHT_VM_DOCKER_BROWSER,\n        image: env.PLAYWRIGHT_VM_DOCKER_IMAGE || undefined,\n        browserBinary: env.PLAYWRIGHT_VM_DOCKER_BROWSER_BINARY || undefined,\n        platform: env.PLAYWRIGHT_VM_DOCKER_PLATFORM || undefined,\n        extensionPath: env.PLAYWRIGHT_VM_DOCKER_EXTENSION_PATH || undefined,\n        userDataDir: env.PLAYWRIGHT_VM_DOCKER_USER_DATA_DIR || undefined,\n        shmSize: env.PLAYWRIGHT_VM_DOCKER_SHM_SIZE || undefined,\n        network: env.PLAYWRIGHT_VM_DOCKER_NETWORK || undefined,\n        runArgs: env.PLAYWRIGHT_VM_DOCKER_RUN_ARGS,\n      },\n    },\n    registry: {\n      environment: env.NODE_ENV || 'development',\n      portRegistryPath: env.PORT_REGISTRY_PATH,\n      processRegistryPath: env.PROCESS_REGISTRY_PATH,\n      host: trimmed(env.PLAYWRIGHT_HOST),\n      port: port !== undefined && port <= 65535 ? port : undefined,\n      allowGlobalReuse: env.BROWSE_TOOL_ALLOW_GLOBAL_HTTP_REUSE === '1',\n    },\n  };\n}\n","import type { CDPSession, Page } from 'playwright';\n\n/** One cached JPEG and its number. The number changes only when the bytes do, and is never reused. */\nexport interface SessionViewFrame {\n  seq: number;\n  data: Buffer;\n}\n\nexport interface SessionViewScreencastOptions {\n  /** JPEG quality for screencast frames, matching the screenshot path. */\n  quality: number;\n  /** Stop a page's screencast after this long without a fetch. */\n  idleMs?: number;\n  /** Past this age the cached frame is replaced by a fresh screenshot. */\n  staleMs?: number;\n  now?: () => number;\n}\n\ninterface ScreencastFramePayload {\n  data: string;\n  sessionId: number;\n}\n\ninterface PageStream {\n  session?: CDPSession;\n  starting?: Promise<void>;\n  /** Not Chromium, or the CDP session refused to start: screenshots only. */\n  unsupported: boolean;\n  latest?: SessionViewFrame & { at: number };\n  idleTimer?: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_IDLE_MS = 2000;\nconst DEFAULT_STALE_MS = 1000;\n\n/**\n * Live frames for the session view. A page's CDP screencast starts on the\n * first fetch, every frame is acknowledged at once so Chrome keeps sending,\n * and only the latest is kept. Viewers pull at their own pace, so a slow\n * viewer drops frames instead of queueing them anywhere.\n *\n * Chrome sends nothing while nothing repaints, and may not repaint a hidden\n * or covered window at all, so a frame older than `staleMs` is replaced by a\n * screenshot. Identical bytes keep the same number.\n */\nexport class SessionViewScreencast {\n  private readonly streams = new Map<string, PageStream>();\n  /** Shared across pages and restarts, so a number a viewer holds is never reused. */\n  private seq = 0;\n  private readonly idleMs: number;\n  private readonly staleMs: number;\n  private readonly now: () => number;\n\n  constructor(private readonly options: SessionViewScreencastOptions) {\n    this.idleMs = options.idleMs ?? DEFAULT_IDLE_MS;\n    this.staleMs = options.staleMs ?? DEFAULT_STALE_MS;\n    this.now = options.now ?? Date.now;\n  }\n\n  /** The latest frame of a page; `screenshot` runs when no fresh screencast frame exists. */\n  async frame(pageId: string, page: Page, screenshot: () => Promise<Buffer>): Promise<SessionViewFrame> {\n    let stream = this.streams.get(pageId);\n    if (!stream) {\n      stream = { unsupported: false };\n      this.streams.set(pageId, stream);\n    }\n    this.keepAlive(pageId, stream);\n    if (!stream.unsupported && !stream.session) {\n      stream.starting ??= this.start(pageId, page, stream).finally(() => {\n        stream.starting = undefined;\n      });\n      await stream.starting;\n    }\n    const latest = stream.latest;\n    if (latest && this.now() - latest.at < this.staleMs) return { seq: latest.seq, data: latest.data };\n    this.store(stream, await screenshot());\n    const stored = stream.latest!;\n    return { seq: stored.seq, data: stored.data };\n  }\n\n  private keepAlive(pageId: string, stream: PageStream): void {\n    clearTimeout(stream.idleTimer);\n    stream.idleTimer = setTimeout(() => void this.stop(pageId), this.idleMs);\n    stream.idleTimer.unref?.();\n  }\n\n  private async start(pageId: string, page: Page, stream: PageStream): Promise<void> {\n    let session: CDPSession;\n    try {\n      session = await page.context().newCDPSession(page);\n    } catch {\n      // Firefox and WebKit have no CDP session; screenshots still work there.\n      stream.unsupported = true;\n      return;\n    }\n    session.on('Page.screencastFrame', (payload: ScreencastFramePayload) => {\n      if (this.streams.get(pageId) !== stream) return;\n      this.store(stream, Buffer.from(payload.data, 'base64'));\n      session.send('Page.screencastFrameAck', { sessionId: payload.sessionId }).catch(() => {\n        // The page or session went away mid-frame; the next fetch screenshots or restarts.\n        void this.stop(pageId);\n      });\n    });\n    try {\n      await session.send('Page.startScreencast', { format: 'jpeg', quality: this.options.quality });\n    } catch {\n      // A Chromium build that refuses screencasts falls back to screenshots for this page.\n      stream.unsupported = true;\n      await session.detach().catch(() => undefined);\n      return;\n    }\n    if (this.streams.get(pageId) !== stream) {\n      // Stopped while starting: nobody owns this session, so end it here.\n      await session.send('Page.stopScreencast').catch(() => undefined);\n      await session.detach().catch(() => undefined);\n      return;\n    }\n    stream.session = session;\n    page.once('close', () => void this.stop(pageId));\n  }\n\n  private store(stream: PageStream, data: Buffer): void {\n    const at = this.now();\n    if (stream.latest?.data.equals(data)) {\n      stream.latest.at = at;\n      return;\n    }\n    stream.latest = { seq: ++this.seq, data, at };\n  }\n\n  private async stop(pageId: string): Promise<void> {\n    const stream = this.streams.get(pageId);\n    if (!stream) return;\n    this.streams.delete(pageId);\n    clearTimeout(stream.idleTimer);\n    const session = stream.session;\n    if (!session) return;\n    // Best effort: a closed page has already dropped its session.\n    await session.send('Page.stopScreencast').catch(() => undefined);\n    await session.detach().catch(() => undefined);\n  }\n}\n","import type { IBrowserService } from './BrowserService.js';\nimport type { IPageRegistry, PageEntry } from './PageRegistry.js';\nimport { SessionViewScreencast } from './SessionViewScreencast.js';\nimport type { ITelemetryService } from './TelemetryService.js';\n\n/** One page an owner holds, as the live view lists it. */\nexport interface SessionViewPage {\n  id: string;\n  /** Origin plus path only. */\n  url: string;\n  title: string;\n  /** Epoch ms of the last tool call against the page. */\n  lastAccessedAt: number;\n  current: boolean;\n}\n\n/** One browser with the owner's pages, or a recent crash that listed the owner. */\nexport interface SessionViewBrowser {\n  id: string;\n  profileName?: string;\n  headless: boolean;\n  /** Epoch ms when the browser went away without browse-tool closing it. */\n  crashedAt?: number;\n  pages: SessionViewPage[];\n}\n\nexport type SessionViewFrameFormat = 'jpeg' | 'png';\n\nexport type SessionViewFrameResult =\n  /** `seq` numbers a jpeg frame. */\n  | { state: 'ok'; bytes: Uint8Array; contentType: string; seq?: number }\n  /** The frame numbered `since` is still current. */\n  | { state: 'unchanged'; seq: number }\n  /** Missing, closed, or owned by someone else: the three look the same. */\n  | { state: 'closed' }\n  | { state: 'timeout' }\n  /** Capture failed, or an extension-mode page, which has no frames. */\n  | { state: 'error' };\n\n/** What the live view reads from the runtime. */\nexport interface SessionViewDeps {\n  pageRegistry: Pick<IPageRegistry, 'get' | 'findByOwner' | 'updateMetadata'>;\n  browserService: Pick<IBrowserService, 'getBrowser' | 'listRecentCrashes'>;\n  telemetry: Pick<ITelemetryService, 'log'>;\n}\n\n/** One owner's browsers and page frames. Reading never counts as page activity. */\nexport interface SessionView {\n  /** Undefined for a malformed owner. */\n  listBrowsers(owner: string): Promise<SessionViewBrowser[] | undefined>;\n  /** `since` is the frame number the caller holds; jpeg only. */\n  fetchFrame(\n    owner: string,\n    pageId: string,\n    format: SessionViewFrameFormat,\n    since?: number,\n  ): Promise<SessionViewFrameResult>;\n}\n\ntype LivePageEntry = PageEntry & { page: NonNullable<PageEntry['page']> };\n\nconst OWNER_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;\nconst METADATA_TIMEOUT_MS = 500;\nconst JPEG_QUALITY = 60;\nconst SCREENSHOT_TIMEOUT_MS = 3000;\nconst CONTENT_TYPES: Record<SessionViewFrameFormat, string> = { jpeg: 'image/jpeg', png: 'image/png' };\n\n/** True when `owner` is a well-formed owner id. */\nexport function isSessionViewOwner(owner: string | undefined): owner is string {\n  return owner !== undefined && OWNER_PATTERN.test(owner);\n}\n\n/** Origin plus path only: query strings and fragments can carry OAuth codes and tokens. */\nfunction toViewUrl(rawUrl: string): string {\n  let parsed: URL;\n  try {\n    parsed = new URL(rawUrl);\n  } catch {\n    return '';\n  }\n  if (parsed.origin !== 'null') {\n    return `${parsed.origin}${parsed.pathname}`;\n  }\n  // about:blank keeps its path; data: and similar would put their payload there.\n  return parsed.protocol === 'about:' ? `${parsed.protocol}${parsed.pathname}` : parsed.protocol;\n}\n\n/**\n * The live view over a runtime's registries. Fails closed: only exact owner matches are listed\n * or captured, and another owner's page answers exactly like a missing one.\n */\nexport function createSessionView({ pageRegistry, browserService, telemetry }: SessionViewDeps): SessionView {\n  // One capture per page and format; concurrent callers share it.\n  const inFlight = new Map<string, Promise<Buffer>>();\n  const screencast = new SessionViewScreencast({ quality: JPEG_QUALITY });\n\n  /** Refreshes url and title, giving up after a cap so a hung page cannot stall the listing. */\n  const refreshMetadata = async (pageId: string): Promise<void> => {\n    let timer: ReturnType<typeof setTimeout> | undefined;\n    const cap = new Promise<void>((resolve) => {\n      timer = setTimeout(resolve, METADATA_TIMEOUT_MS);\n    });\n    try {\n      await Promise.race([pageRegistry.updateMetadata(pageId), cap]);\n    } catch {\n      // Documented fallback: the listing shows the last known url and title.\n    } finally {\n      clearTimeout(timer);\n    }\n  };\n\n  const capture = (entry: LivePageEntry, format: SessionViewFrameFormat): Promise<Buffer> => {\n    const key = `${entry.id}:${format}`;\n    const pending = inFlight.get(key);\n    if (pending) {\n      return pending;\n    }\n    const next = entry.page\n      .screenshot({\n        type: format,\n        ...(format === 'jpeg' ? { quality: JPEG_QUALITY } : {}),\n        scale: 'css',\n        timeout: SCREENSHOT_TIMEOUT_MS,\n      })\n      .finally(() => inFlight.delete(key));\n    inFlight.set(key, next);\n    return next;\n  };\n\n  return {\n    async listBrowsers(owner) {\n      if (!isSessionViewOwner(owner)) {\n        return undefined;\n      }\n      const owned = pageRegistry\n        .findByOwner(owner)\n        .filter((entry) => entry.ownerId === owner && entry.mode === 'playwright' && entry.page);\n      await Promise.all(owned.map((entry) => refreshMetadata(entry.id)));\n\n      const browsers = new Map<string, SessionViewBrowser>();\n      for (const entry of owned) {\n        const instance = browserService.getBrowser(entry.browserId);\n        if (!instance) {\n          continue;\n        }\n        let view = browsers.get(instance.id);\n        if (!view) {\n          view = { id: instance.id, profileName: instance.profileName, headless: instance.headless ?? true, pages: [] };\n          browsers.set(instance.id, view);\n        }\n        view.pages.push({\n          id: entry.id,\n          url: toViewUrl(entry.url),\n          title: entry.title,\n          lastAccessedAt: entry.lastAccessedAt.getTime(),\n          current: instance.currentPageId === entry.id,\n        });\n      }\n\n      for (const crash of browserService.listRecentCrashes()) {\n        if (!crash.ownerIds.includes(owner) || browserService.getBrowser(crash.browserId)) {\n          continue;\n        }\n        const existing = browsers.get(crash.browserId);\n        if (existing?.crashedAt !== undefined && existing.crashedAt >= crash.at) {\n          continue;\n        }\n        browsers.set(crash.browserId, {\n          id: crash.browserId,\n          profileName: crash.profileName,\n          headless: crash.headless ?? true,\n          crashedAt: crash.at,\n          pages: [],\n        });\n      }\n      return [...browsers.values()];\n    },\n\n    async fetchFrame(owner, pageId, format, since) {\n      if (!isSessionViewOwner(owner)) {\n        return { state: 'closed' };\n      }\n      const entry = pageRegistry.get(pageId);\n      if (!entry || entry.ownerId !== owner) {\n        return { state: 'closed' };\n      }\n      const page = entry.page;\n      if (entry.mode !== 'playwright' || !page) {\n        return { state: 'error' };\n      }\n      const live = { ...entry, page };\n      try {\n        if (format === 'png') {\n          return { state: 'ok', bytes: await capture(live, format), contentType: CONTENT_TYPES.png };\n        }\n        const frame = await screencast.frame(pageId, page, () => capture(live, format));\n        if (since === frame.seq) {\n          return { state: 'unchanged', seq: frame.seq };\n        }\n        return { state: 'ok', bytes: frame.data, contentType: CONTENT_TYPES.jpeg, seq: frame.seq };\n      } catch (error) {\n        const errorName = error instanceof Error ? error.name : 'Error';\n        // Page id and error name only: messages can quote the page URL.\n        telemetry.log('warn', 'browse-tool session-view frame capture failed', {\n          attributes: { 'browse_tool.page.id': pageId, 'error.type': errorName },\n        });\n        if (!pageRegistry.get(pageId) || page.isClosed()) {\n          return { state: 'closed' };\n        }\n        return errorName === 'TimeoutError' ? { state: 'timeout' } : { state: 'error' };\n      }\n    },\n  };\n}\n","/**\n * WebSocket Routes for Extension Communication\n *\n * DESIGN PATTERNS:\n * - WebSocket upgrade handler using @hono/node-ws\n * - Services passed in as a plain dependencies object\n * - Pub/sub pattern via WebSocketHub\n *\n * CODING STANDARDS:\n * - Use Hono WebSocket helpers for upgrade\n * - Keep route handlers thin, delegate to WebSocketHub\n * - Handle connection lifecycle properly\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n * - Memory leaks from uncleaned connections\n */\n\nimport type { Hono } from 'hono';\nimport type { UpgradeWebSocket } from 'hono/ws';\n\nimport type { BrowseRuntime } from '../container/index.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\n\n/**\n * Setup WebSocket routes for extension communication\n *\n * @param app - Hono app instance\n * @param deps - Holds the WebSocketHub\n * @param upgradeWebSocket - WebSocket upgrade function from @hono/node-ws\n */\nexport function setupWebSocketRoutes(\n  app: Hono,\n  deps: { webSocketHub: WebSocketHub },\n  upgradeWebSocket: UpgradeWebSocket,\n): void {\n  /**\n   * WebSocket endpoint for extension connections\n   * Path: /ws/extension/:browserId\n   */\n  app.get(\n    '/ws/extension/:browserId',\n    upgradeWebSocket((c) => {\n      const browserId = c.req.param('browserId');\n      let connectionId: string | null = null;\n\n      return {\n        onOpen(_event, ws) {\n          if (!browserId) {\n            ws.close(1008, 'Missing browserId');\n            return;\n          }\n          try {\n            const wsHub = deps.webSocketHub;\n            connectionId = wsHub.addConnection(ws, browserId);\n          } catch {\n            ws.close(1011, 'Internal server error');\n          }\n        },\n\n        async onMessage(event) {\n          if (!connectionId) {\n            return;\n          }\n\n          try {\n            const wsHub = deps.webSocketHub;\n            const data =\n              typeof event.data === 'string'\n                ? event.data\n                : new TextDecoder().decode(event.data instanceof Blob ? await event.data.arrayBuffer() : event.data);\n            wsHub.handleMessage(connectionId, data);\n          } catch {\n            // Error handling delegated to WebSocketHub\n          }\n        },\n\n        onClose() {\n          if (connectionId) {\n            try {\n              const wsHub = deps.webSocketHub;\n              wsHub.removeConnection(connectionId);\n            } catch {\n              // Ignore cleanup errors\n            }\n          }\n        },\n\n        onError() {\n          if (connectionId) {\n            try {\n              const wsHub = deps.webSocketHub;\n              wsHub.removeConnection(connectionId);\n            } catch {\n              // Ignore cleanup errors\n            }\n          }\n        },\n      };\n    }),\n  );\n\n  /**\n   * GET /ws/stats\n   * Get WebSocket connection statistics\n   */\n  app.get('/ws/stats', (c) => {\n    try {\n      const wsHub = deps.webSocketHub;\n      const stats = wsHub.getStats();\n\n      return c.json({\n        totalConnections: stats.totalConnections,\n        browserCount: stats.browserCount,\n        connections: stats.connections.map((conn) => ({\n          id: conn.id,\n          browserId: conn.browserId,\n          connectedAt: conn.connectedAt.toISOString(),\n        })),\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n}\n\n/** The runtime parts the extension hub handlers use. */\nexport type ExtensionHubDeps = Pick<\n  BrowseRuntime,\n  'webSocketHub' | 'extensionTaskQueue' | 'browserService' | 'pageRegistry' | 'extensionSessionRegistry'\n>;\n\n/**\n * Handle extension session registration, task results, tab mapping and\n * disconnects on the runtime's WebSocketHub, and start its liveness sweep.\n *\n * @returns Removes the handlers and stops the sweep\n */\nexport function wireExtensionHub(runtime: ExtensionHubDeps): () => void {\n  const wsHub = runtime.webSocketHub;\n  const taskQueue = runtime.extensionTaskQueue;\n  const { browserService, pageRegistry } = runtime;\n  const sessionRegistry = runtime.extensionSessionRegistry;\n\n  wsHub.setEventHandlers({\n    onSessionRegister: (connection, message) => {\n      if (message.type === 'session:register') {\n        const requestedBrowserId = message.payload.browserId || connection.browserId;\n        const existingBrowser = browserService.getBrowser(requestedBrowserId);\n\n        let browserId: string;\n        let pageId: string;\n\n        if (existingBrowser && (existingBrowser.mode === 'extension' || existingBrowser.mode === 'vm')) {\n          browserId = existingBrowser.id;\n          const existingPages = pageRegistry.findByBrowser(browserId);\n          pageId = existingBrowser.currentPageId || existingPages[0]?.id || '';\n\n          wsHub.reassignConnection(connection.id, browserId);\n\n          if (!pageId) {\n            pageId = pageRegistry.registerExtensionPage(browserId);\n            existingBrowser.pageIds.add(pageId);\n            existingBrowser.currentPageId = pageId;\n          }\n\n          console.log(`[WebSocket] Extension connected to existing browser: ${browserId}, pageId: ${pageId}`);\n        } else {\n          browserId = requestedBrowserId;\n          browserService.registerExtensionBrowserWithId(browserId);\n          pageId = pageRegistry.registerExtensionPage(browserId);\n\n          const browserInstance = browserService.getBrowser(browserId);\n          if (browserInstance) {\n            browserInstance.pageIds.add(pageId);\n            browserInstance.currentPageId = pageId;\n          }\n\n          console.log(`[WebSocket] Extension connected with new browser: ${browserId}, pageId: ${pageId}`);\n        }\n\n        const pageEntry = pageRegistry.get(pageId);\n        if (pageEntry) {\n          pageEntry.extensionTabId = message.payload.tabId;\n          pageEntry.url = message.payload.url ?? pageEntry.url;\n        }\n\n        const session = sessionRegistry.register({\n          browserId,\n          tabId: message.payload.tabId,\n          url: message.payload.url,\n          metadata: {\n            transport: 'websocket',\n          },\n        });\n        wsHub.sendSessionAck(connection, message.id ?? '', session.id, session.controlMode);\n\n        wsHub.broadcastPageCreated(browserId, pageId, pageRegistry.get(pageId)?.url);\n      }\n    },\n    onTaskResult: (_connection, message) => {\n      if (message.type === 'task:result') {\n        const task = taskQueue.submitResult({\n          taskId: message.payload.taskId,\n          success: message.payload.success,\n          result: message.payload.result,\n          error: message.payload.error,\n        });\n\n        if (task?.browserId) {\n          browserService.recordBrowserActivity(task.browserId, task.pageId);\n        }\n      }\n    },\n    onTabMapped: (_connection, message) => {\n      if (message.type === 'tab:mapped') {\n        const pageEntry = pageRegistry.get(message.payload.pageId);\n        if (!pageEntry) {\n          return;\n        }\n\n        pageEntry.extensionTabId = message.payload.tabId;\n        browserService.recordBrowserActivity(pageEntry.browserId, pageEntry.id);\n      }\n    },\n    onDisconnect: (connection) => {\n      if (connection.sessionId) {\n        sessionRegistry.removeSession(connection.sessionId);\n      }\n      console.log(`[WebSocket] Extension disconnected: ${connection.browserId}`);\n\n      // A reconnect leaves another live connection, so only fail the browser's\n      // work once nothing can deliver it. Otherwise callers wait out the full\n      // task timeout for a result that can never arrive.\n      if (!wsHub.hasConnection(connection.browserId)) {\n        taskQueue.failPendingTasksForBrowser(\n          connection.browserId,\n          `Extension for browser \"${connection.browserId}\" disconnected`,\n        );\n      }\n    },\n  });\n\n  // Sockets can die without a close frame (laptop suspend, crashed\n  // extension); this drops them so pushes fall back to HTTP polling.\n  wsHub.startLivenessSweep();\n\n  return () => {\n    wsHub.stopLivenessSweep();\n    wsHub.setEventHandlers({});\n  };\n}\n"],"mappings":"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"}