@bbearai/ai-executor 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-WT22IQMS.mjs +175 -0
- package/dist/chunk-WT22IQMS.mjs.map +1 -0
- package/dist/cli.js +622 -129
- package/dist/cli.js.map +1 -1
- package/dist/index.d.mts +533 -8
- package/dist/index.d.ts +533 -8
- package/dist/index.js +1613 -131
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1411 -130
- package/dist/index.mjs.map +1 -1
- package/dist/report-generator-EVZEB33O.mjs +7 -0
- package/dist/report-generator-EVZEB33O.mjs.map +1 -0
- package/package.json +5 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner.ts","../src/browser.ts","../src/evaluator.ts"],"sourcesContent":["/**\n * Test Runner\n *\n * Orchestrates the full test execution lifecycle using Stagehand:\n * 1. Launch Stagehand browser session\n * 2. Navigate to target URL\n * 3. Inject authentication\n * 4. For each step: act() → screenshot → extract() → record\n * 5. Generate summary\n * 6. Return structured results\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { z } from 'zod';\nimport { createStagehandSession, injectAuth } from './browser';\nimport { generateRunSummary } from './evaluator';\nimport type {\n TestRunConfig,\n TestRunResult,\n StepResult,\n OverallResult,\n BrowserConfig,\n ConsoleEntry,\n NetworkError,\n} from './types';\n\n/**\n * Execute a test case against a target URL using Stagehand.\n */\nexport async function runTest(config: TestRunConfig): Promise<TestRunResult> {\n const anthropic = new Anthropic({ apiKey: config.anthropicApiKey });\n const startTime = Date.now();\n\n const browserConfig: BrowserConfig = config.browser ?? {\n provider: 'local',\n headless: true,\n };\n\n config.onStatusChange?.('initializing');\n\n const session = await createStagehandSession(browserConfig, config.anthropicApiKey);\n const { stagehand, page } = session;\n\n const stepResults: StepResult[] = [];\n\n // Accumulated console logs and network errors — drained per step\n let pendingConsoleLogs: ConsoleEntry[] = [];\n let pendingNetworkErrors: NetworkError[] = [];\n let stepStartTime = Date.now();\n\n // Cast to any for event listeners — Stagehand's Page type only exposes 'console'\n // but the underlying Playwright page supports all standard events\n const rawPage = page as any;\n\n // Listen for console messages\n rawPage.on('console', (msg: any) => {\n const level = msg.type?.() ?? msg.type ?? 'log';\n const mappedLevel =\n level === 'error' ? 'error' :\n level === 'warn' || level === 'warning' ? 'warning' :\n level === 'info' ? 'info' :\n level === 'debug' ? 'debug' : 'log';\n\n pendingConsoleLogs.push({\n level: mappedLevel as ConsoleEntry['level'],\n text: (typeof msg.text === 'function' ? msg.text() : String(msg.text ?? msg)).slice(0, 2000),\n source: typeof msg.location === 'function' ? msg.location()?.url : undefined,\n timestamp: Date.now() - stepStartTime,\n });\n });\n\n // Listen for failed requests (connection errors, CORS, etc.)\n rawPage.on('requestfailed', (req: any) => {\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - stepStartTime,\n });\n });\n\n // Listen for HTTP error responses (4xx/5xx)\n rawPage.on('response', (res: any) => {\n const status = typeof res.status === 'function' ? res.status() : Number(res.status ?? 0);\n if (status >= 400) {\n const url = typeof res.url === 'function' ? res.url() : String(res.url ?? '');\n const statusText = typeof res.statusText === 'function' ? res.statusText() : String(res.statusText ?? '');\n const req = typeof res.request === 'function' ? res.request() : res.request;\n const method = req ? (typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET')) : 'GET';\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status,\n statusText,\n timestamp: Date.now() - stepStartTime,\n });\n }\n });\n\n try {\n // Handle form-login auth first (navigates to login page, then we go to target)\n if (config.auth?.type === 'form-login') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n }\n\n // Navigate to target URL\n config.onStatusChange?.('navigating');\n const targetUrl = config.testCase.targetRoute\n ? `${config.targetUrl.replace(/\\/$/, '')}${config.testCase.targetRoute}`\n : config.targetUrl;\n\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n\n // Inject non-form authentication after navigation\n if (config.auth && config.auth.type !== 'form-login') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n\n // If localStorage auth, reload to apply\n if (config.auth.type === 'localStorage') {\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, config.auth.items);\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n }\n\n // Wait for page to stabilize\n await page.waitForLoadState('networkidle').catch(() => {});\n\n // Clear any logs accumulated during navigation/auth\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Execute each step using Stagehand\n config.onStatusChange?.('executing');\n const steps = config.testCase.steps;\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n stepStartTime = Date.now();\n\n // Reset per-step collectors\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Capture pre-action screenshot\n const screenshotBefore = await page.screenshot({ type: 'png' });\n\n let error: string | undefined;\n let screenshotAfter: Buffer = screenshotBefore;\n let actSucceeded = false;\n\n // Execute the action using Stagehand\n try {\n await stagehand.act(step.action);\n actSucceeded = true;\n\n // Wait for page to settle after action\n await page.waitForLoadState('networkidle').catch(() => {});\n await page.waitForTimeout(500);\n\n // Capture post-action screenshot\n screenshotAfter = await page.screenshot({ type: 'png' });\n } catch (err) {\n error = err instanceof Error ? err.message : String(err);\n // Still capture screenshot for debugging even after failure\n screenshotAfter = await page.screenshot({ type: 'png' }).catch(() => screenshotBefore);\n }\n\n // Evaluate the result using Stagehand's extract()\n let evaluation = {\n passed: false,\n confidence: 0,\n actualResult: error ?? 'Action execution failed',\n };\n\n if (actSucceeded) {\n try {\n const verificationSchema = z.object({\n passed: z.boolean().describe('Whether the expected result was achieved'),\n confidence: z\n .number()\n .min(0)\n .max(1)\n .describe('Confidence in the assessment (0.9+ = very sure, 0.7-0.9 = likely, below 0.7 = uncertain)'),\n actualResult: z.string().describe('Description of what actually happened on the page'),\n });\n\n const verification = await stagehand.extract(\n `You are evaluating a QA test step. The action \"${step.action}\" was just performed. ` +\n `Check if this expected result was achieved: \"${step.expectedResult}\". ` +\n `Look at the current page state and describe what actually happened. ` +\n `Be precise and factual in your assessment.`,\n verificationSchema,\n );\n\n evaluation = {\n passed: verification.passed,\n confidence: verification.confidence,\n actualResult: verification.actualResult,\n };\n } catch (evalErr) {\n evaluation = {\n passed: false,\n confidence: 0.2,\n actualResult: `Verification error: ${evalErr instanceof Error ? evalErr.message : String(evalErr)}`,\n };\n }\n }\n\n // Snapshot the collected logs for this step (limit to 50 entries each to avoid bloat)\n const consoleLogs = pendingConsoleLogs.slice(0, 50);\n const networkErrors = pendingNetworkErrors.slice(0, 30);\n\n const result: StepResult = {\n stepNumber: step.stepNumber,\n action: step.action,\n expectedResult: step.expectedResult,\n actualResult: evaluation.actualResult,\n passed: evaluation.passed,\n confidence: evaluation.confidence,\n screenshotBefore,\n screenshotAfter,\n actionsTaken: [], // Stagehand handles actions internally\n error,\n durationMs: Date.now() - stepStartTime,\n consoleLogs,\n networkErrors,\n };\n\n stepResults.push(result);\n config.onStepComplete?.(result, i, steps.length);\n }\n\n // Generate summary\n config.onStatusChange?.('completed');\n const model = config.model ?? 'claude-sonnet-4-20250514';\n const summary = await generateRunSummary(anthropic, config.testCase.title, stepResults, model);\n\n // Determine overall result\n const overallResult = determineOverallResult(stepResults);\n\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult,\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary,\n screenshotUrls: [],\n tokenUsage: {\n // Stagehand tracks tokens internally; these are approximate\n inputTokens: steps.length * 3000,\n outputTokens: steps.length * 500,\n },\n browserSessionId: session.sessionId,\n };\n } catch (err) {\n // Fatal error — test couldn't complete\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult: 'error',\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary: `Test execution failed: ${err instanceof Error ? err.message : String(err)}`,\n screenshotUrls: [],\n tokenUsage: {\n inputTokens: stepResults.length * 3000,\n outputTokens: stepResults.length * 500,\n },\n browserSessionId: session.sessionId,\n };\n } finally {\n await session.close();\n }\n}\n\n/**\n * Determine the overall test result from step results.\n */\nfunction determineOverallResult(steps: StepResult[]): OverallResult {\n if (steps.length === 0) return 'error';\n\n const allPassed = steps.every((s) => s.passed);\n const allFailed = steps.every((s) => !s.passed);\n const hasErrors = steps.some((s) => s.error);\n\n if (allPassed) return 'passed';\n if (allFailed || hasErrors) return 'failed';\n return 'partial';\n}\n","/**\n * Browser Provider\n *\n * Uses Stagehand (by Browserbase) for AI-powered browser automation.\n * Supports both Browserbase hosted sessions and local Playwright.\n */\n\nimport { Stagehand } from '@browserbasehq/stagehand';\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { BrowserConfig, AuthConfig } from './types';\n\nconst DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n\nexport interface StagehandSession {\n stagehand: Stagehand;\n page: Page;\n sessionId: string;\n close: () => Promise<void>;\n}\n\n/**\n * Create a Stagehand-powered browser session.\n *\n * Stagehand wraps Playwright with AI capabilities (act, extract, observe)\n * and manages Browserbase or local browser sessions.\n */\nexport async function createStagehandSession(\n config: BrowserConfig,\n anthropicApiKey: string,\n): Promise<StagehandSession> {\n const modelName = config.model ?? DEFAULT_MODEL;\n const viewport = config.viewport ?? { width: 1280, height: 720 };\n\n const stagehand = new Stagehand({\n env: config.provider === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',\n apiKey: config.provider === 'browserbase' ? config.browserbaseApiKey : undefined,\n projectId: config.provider === 'browserbase' ? config.browserbaseProjectId : undefined,\n model: {\n modelName: modelName as any,\n apiKey: anthropicApiKey,\n },\n localBrowserLaunchOptions:\n config.provider === 'local'\n ? {\n headless: config.headless ?? true,\n viewport,\n }\n : undefined,\n browserbaseSessionCreateParams:\n config.provider === 'browserbase'\n ? { projectId: config.browserbaseProjectId! }\n : undefined,\n });\n\n await stagehand.init();\n\n const page = stagehand.context.activePage()!;\n await page.setViewportSize(viewport.width, viewport.height);\n\n // Extract session ID\n let sessionId = `local-${Date.now()}`;\n if (config.provider === 'browserbase' && stagehand.browserbaseSessionID) {\n sessionId = stagehand.browserbaseSessionID;\n }\n\n return {\n stagehand,\n page,\n sessionId,\n close: async () => {\n await stagehand.close().catch(() => {});\n },\n };\n}\n\n/**\n * Inject authentication into the browser session.\n * Uses Stagehand's Page API and CDP for cookie injection.\n */\nexport async function injectAuth(page: Page, auth: AuthConfig, stagehand?: Stagehand): Promise<void> {\n if (auth.type === 'cookie') {\n // Use CDP to set cookies (Stagehand's Page doesn't have context().addCookies)\n for (const c of auth.cookies) {\n await page.sendCDP('Network.setCookie', {\n name: c.name,\n value: c.value,\n domain: c.domain,\n path: c.path ?? '/',\n secure: c.secure ?? false,\n httpOnly: c.httpOnly ?? false,\n sameSite: c.sameSite ?? 'Lax',\n });\n }\n } else if (auth.type === 'localStorage') {\n const currentUrl = page.url();\n if (currentUrl === 'about:blank') {\n // We'll inject localStorage after navigation in the runner\n return;\n }\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, auth.items);\n } else if (auth.type === 'form-login') {\n await performFormLogin(page, auth, stagehand);\n }\n}\n\n/**\n * Automate login via a web form.\n *\n * Uses Stagehand's act() when available for AI-powered form filling,\n * falling back to manual locator-based login.\n */\nasync function performFormLogin(\n page: Page,\n auth: { loginUrl: string; email: string; password: string },\n stagehand?: Stagehand,\n): Promise<void> {\n await page.goto(auth.loginUrl, { waitUntil: 'domcontentloaded' });\n\n // Wait for the page to settle\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n\n if (stagehand) {\n // Use Stagehand's AI to handle login — much more reliable\n await stagehand.act(\n `Fill in the email/username field with \"${auth.email}\" and the password field with \"${auth.password}\", then click the login/sign-in button to submit the form.`,\n );\n } else {\n // Fallback: manual locator-based login\n await manualFormLogin(page, auth);\n }\n\n // Wait for navigation after login\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n}\n\n/**\n * Manual form login using locators (fallback when Stagehand instance unavailable).\n */\nasync function manualFormLogin(\n page: Page,\n auth: { email: string; password: string },\n): Promise<void> {\n // Wait for a form or input to appear\n await page\n .waitForSelector(\n 'input[type=\"email\"], input[type=\"text\"][name*=\"email\"], input[name*=\"user\"], input[type=\"text\"]',\n { timeout: 15_000 },\n )\n .catch(() => {});\n\n // Try common email/username selectors in priority order\n const emailSelectors = [\n 'input[type=\"email\"]',\n 'input[name=\"email\"]',\n 'input[name=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[autocomplete=\"username\"]',\n 'input[type=\"text\"][name*=\"email\"]',\n 'input[type=\"text\"][name*=\"user\"]',\n 'input[type=\"text\"]',\n ];\n\n let emailFilled = false;\n for (const sel of emailSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.fill(auth.email);\n emailFilled = true;\n break;\n }\n }\n\n if (!emailFilled) {\n throw new Error('Could not find email/username input on login page');\n }\n\n // Fill password\n const passwordLocator = page.locator('input[type=\"password\"]');\n if ((await passwordLocator.count()) > 0 && (await passwordLocator.isVisible())) {\n await passwordLocator.fill(auth.password);\n } else {\n throw new Error('Could not find password input on login page');\n }\n\n // Submit the form\n const submitSelectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n ];\n\n let submitted = false;\n for (const sel of submitSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.click();\n submitted = true;\n break;\n }\n }\n\n if (!submitted) {\n // Fallback: press Enter on the password field\n await page.locator('input[type=\"password\"]').type('\\n');\n }\n}\n","/**\n * Result Evaluator\n *\n * Generates AI summaries of test run results.\n * Step-level evaluation is now handled by Stagehand's extract() in the runner.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\n/**\n * Generate an AI summary of the entire test run.\n */\nexport async function generateRunSummary(\n anthropic: Anthropic,\n testTitle: string,\n steps: Array<{\n stepNumber: number;\n action: string;\n expectedResult: string;\n actualResult: string;\n passed: boolean;\n confidence: number;\n error?: string;\n }>,\n model: string,\n): Promise<string> {\n const stepsText = steps\n .map(\n (s) =>\n `Step ${s.stepNumber}: ${s.action}\\n Expected: ${s.expectedResult}\\n Actual: ${s.actualResult}\\n Result: ${s.passed ? 'PASS' : 'FAIL'} (confidence: ${Math.round(s.confidence * 100)}%)${s.error ? `\\n Error: ${s.error}` : ''}`\n )\n .join('\\n\\n');\n\n const passCount = steps.filter((s) => s.passed).length;\n const failCount = steps.filter((s) => !s.passed).length;\n\n const response = await anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: `Summarize this AI test execution in 2-3 sentences. Focus on what was tested, what passed, and what failed (if anything). Be concise and factual.\n\nTest: ${testTitle}\nResults: ${passCount} passed, ${failCount} failed out of ${steps.length} steps\n\n${stepsText}`,\n },\n ],\n });\n\n return response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/runner.ts","../src/browser.ts","../src/supabase-auth.ts","../src/evaluator.ts","../src/vision-evaluator.ts","../src/action-executor.ts","../src/selector-discovery.ts","../src/cost.ts","../src/explorer.ts","../src/report-triager.ts","../src/failure-analyzer.ts","../src/concurrency.ts"],"sourcesContent":["/**\n * Test Runner\n *\n * Orchestrates the full test execution lifecycle:\n * 1. Launch Stagehand browser session\n * 2. Navigate to target URL\n * 3. Inject authentication (supports supabase-native, cookie, localStorage, form-login)\n * 4. For each step: act() → screenshot → vision evaluate → record\n * 5. Generate summary\n * 6. Return structured results\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { createStagehandSession, injectAuth, suppressBugBearWidget } from './browser';\nimport { generateRunSummary } from './evaluator';\nimport { evaluateStep } from './vision-evaluator';\nimport { executeAction } from './action-executor';\nimport { discoverSelector, installClickTracker } from './selector-discovery';\nimport { getTokenEstimate } from './cost';\nimport type {\n TestRunConfig,\n TestRunResult,\n StepResult,\n OverallResult,\n BrowserConfig,\n ConsoleEntry,\n NetworkError,\n RetryAttempt,\n} from './types';\n\n/** Default timeout for AI operations (30 seconds) */\nconst AI_OPERATION_TIMEOUT_MS = 30_000;\n\n/** Default retry settings */\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_RETRY_DELAY_MS = 2_000;\n\n/**\n * Detect if an error is transient and worth retrying.\n * Only retries infrastructure-level failures, not evaluation disagreements.\n */\nfunction isRetryableError(error: string): boolean {\n const patterns = [\n /timed?\\s*out/i,\n /ECONNREFUSED/i,\n /ECONNRESET/i,\n /ENOTFOUND/i,\n /net::ERR_/i,\n /navigation failed/i,\n /page crashed/i,\n /context was destroyed/i,\n /target closed/i,\n /session closed/i,\n /browser disconnected/i,\n /execution context/i,\n ];\n return patterns.some(p => p.test(error));\n}\n\n/** Wrap a promise with a timeout */\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`${operation} timed out after ${timeoutMs}ms`)), timeoutMs);\n });\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n}\n\n/**\n * Execute a test case against a target URL using Stagehand.\n */\nexport async function runTest(config: TestRunConfig): Promise<TestRunResult> {\n const anthropic = new Anthropic({ apiKey: config.anthropicApiKey });\n const startTime = Date.now();\n\n const browserConfig: BrowserConfig = config.browser ?? {\n provider: 'local',\n headless: true,\n };\n\n config.onStatusChange?.('initializing');\n\n let session: Awaited<ReturnType<typeof createStagehandSession>> | undefined;\n\n const stepResults: StepResult[] = [];\n\n // Accumulated console logs and network errors — drained per step\n let pendingConsoleLogs: ConsoleEntry[] = [];\n let pendingNetworkErrors: NetworkError[] = [];\n let stepStartTime = Date.now();\n\n try {\n session = await createStagehandSession(browserConfig, config.anthropicApiKey);\n const { stagehand, page } = session;\n\n // Suppress the BugBear widget to prevent it from intercepting clicks,\n // covering UI elements, or triggering popups during test execution.\n await suppressBugBearWidget(stagehand);\n\n // Stagehand's Page wrapper only supports a subset of Playwright events.\n // All listeners are wrapped in try/catch — unsupported events throw\n // InvalidArgumentError at registration time. Core test execution\n // (navigate, screenshot, Claude analysis) works without these.\n const rawPage = page as any;\n\n // Console messages — usually supported by Stagehand\n try {\n rawPage.on('console', (msg: any) => {\n const level = msg.type?.() ?? msg.type ?? 'log';\n const mappedLevel =\n level === 'error' ? 'error' :\n level === 'warn' || level === 'warning' ? 'warning' :\n level === 'info' ? 'info' :\n level === 'debug' ? 'debug' : 'log';\n\n pendingConsoleLogs.push({\n level: mappedLevel as ConsoleEntry['level'],\n text: (typeof msg.text === 'function' ? msg.text() : String(msg.text ?? msg)).slice(0, 2000),\n source: typeof msg.location === 'function' ? msg.location()?.url : undefined,\n timestamp: Date.now() - stepStartTime,\n });\n });\n } catch {\n // Console capture unavailable\n }\n\n // Failed requests (connection errors, CORS, etc.)\n try {\n rawPage.on('requestfailed', (req: any) => {\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - stepStartTime,\n });\n });\n } catch {\n // requestfailed capture unavailable\n }\n\n // HTTP error responses (4xx/5xx)\n try {\n rawPage.on('response', (res: any) => {\n const status = typeof res.status === 'function' ? res.status() : Number(res.status ?? 0);\n if (status >= 400) {\n const url = typeof res.url === 'function' ? res.url() : String(res.url ?? '');\n const statusText = typeof res.statusText === 'function' ? res.statusText() : String(res.statusText ?? '');\n const req = typeof res.request === 'function' ? res.request() : res.request;\n const method = req ? (typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET')) : 'GET';\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status,\n statusText,\n timestamp: Date.now() - stepStartTime,\n });\n }\n });\n } catch {\n // HTTP error capture unavailable\n }\n // Handle pre-navigation auth (form-login and supabase-native inject before navigation)\n if (config.auth?.type === 'form-login' || config.auth?.type === 'supabase-native') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n }\n\n // Navigate to target URL\n config.onStatusChange?.('navigating');\n const targetUrl = config.testCase.targetRoute\n ? `${config.targetUrl.replace(/\\/$/, '')}${config.testCase.targetRoute}`\n : config.targetUrl;\n\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n\n // Inject post-navigation auth (cookie, localStorage)\n if (config.auth && config.auth.type !== 'form-login' && config.auth.type !== 'supabase-native') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n\n // If localStorage auth, reload to apply\n if (config.auth.type === 'localStorage') {\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, config.auth.items);\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n }\n\n // Wait for page to stabilize\n await page.waitForLoadState('networkidle').catch(() => {});\n\n // Belt-and-suspenders: also set the suppression flag via evaluate in case\n // addInitScript wasn't available in the Stagehand/Playwright version.\n await page.evaluate(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n }).catch(() => {});\n\n // Install click tracker for selector discovery\n await installClickTracker(page);\n\n // Clear any logs accumulated during navigation/auth\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Execute each step (deterministic Playwright when possible, Stagehand fallback)\n config.onStatusChange?.('executing');\n const steps = config.testCase.steps;\n const maxRetries = config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES;\n const retryDelayMs = config.retry?.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n const resilientMode = config.resilientMode ?? true;\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n const retryHistory: RetryAttempt[] = [];\n let finalResult: StepResult | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n stepStartTime = Date.now();\n\n // Reset per-step collectors\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Capture pre-action screenshot\n const screenshotBefore = await page.screenshot({ type: 'png' });\n\n let error: string | undefined;\n let screenshotAfter: Buffer = screenshotBefore;\n let actSucceeded = false;\n\n // Execute the action (deterministic Playwright when possible, Stagehand fallback)\n const actionResult = await executeAction(page, stagehand, step);\n error = actionResult.error;\n actSucceeded = !error;\n\n if (actSucceeded) {\n // Wait for page to settle after action\n await page.waitForLoadState('networkidle').catch(() => {});\n await page.waitForTimeout(step.waitMs ?? 500);\n }\n\n // Capture post-action screenshot\n screenshotAfter = await page.screenshot({ type: 'png' }).catch(() => screenshotBefore);\n\n // Evaluate the result using Claude Vision (before/after screenshots)\n let evaluation = {\n passed: false,\n confidence: 0,\n actualResult: error ?? 'Action execution failed',\n };\n\n if (actSucceeded) {\n try {\n const visionResult = await withTimeout(\n evaluateStep({\n anthropic,\n screenshotBefore,\n screenshotAfter,\n action: step.action,\n expectedResult: step.expectedResult,\n evaluationHint: step.evaluationHint,\n model: config.model,\n }),\n AI_OPERATION_TIMEOUT_MS,\n 'Vision evaluation',\n );\n\n evaluation = {\n passed: visionResult.passed,\n confidence: visionResult.confidence,\n actualResult: visionResult.actualResult,\n };\n } catch (evalErr) {\n evaluation = {\n passed: false,\n confidence: 0.2,\n actualResult: `Vision evaluation error: ${evalErr instanceof Error ? evalErr.message : String(evalErr)}`,\n };\n }\n }\n\n // Selector discovery — when Stagehand handled the step, try to find what was clicked\n let discoveredActions: { type: string; selector?: string; description: string }[] = [];\n if (actSucceeded && !actionResult.deterministic) {\n const discovered = await discoverSelector(page);\n if (discovered) {\n discoveredActions = [{\n type: discovered.suggestedActionType ?? 'click',\n selector: discovered.selector,\n description: `Discovered via ${discovered.strategy}: ${discovered.tagName}${discovered.textContent ? ` \"${discovered.textContent.slice(0, 50)}\"` : ''}`,\n }];\n }\n }\n\n // Snapshot the collected logs for this step\n const consoleLogs = pendingConsoleLogs.slice(0, 50);\n const networkErrors = pendingNetworkErrors.slice(0, 30);\n\n finalResult = {\n stepNumber: step.stepNumber,\n action: step.action,\n expectedResult: step.expectedResult,\n actualResult: evaluation.actualResult,\n passed: evaluation.passed,\n confidence: evaluation.confidence,\n screenshotBefore,\n screenshotAfter,\n actionsTaken: discoveredActions,\n error,\n durationMs: Date.now() - stepStartTime,\n consoleLogs,\n networkErrors,\n retryCount: attempt,\n retryHistory,\n skipped: false,\n };\n\n // Decide whether to retry: only retry transient errors, not evaluation failures\n const shouldRetry = !evaluation.passed\n && error\n && isRetryableError(error)\n && attempt < maxRetries;\n\n if (!shouldRetry) break;\n\n // Record this attempt in retry history before retrying\n retryHistory.push({\n attempt,\n error: error!,\n confidence: evaluation.confidence,\n timestamp: Date.now(),\n });\n\n // Wait before retrying\n await new Promise(resolve => setTimeout(resolve, retryDelayMs));\n }\n\n // ── Skip-and-recover: mark failed steps as skipped, recover page state ──\n if (resilientMode && finalResult && !finalResult.passed) {\n finalResult.skipped = true;\n finalResult.skipReason = 'Step failed, recovered page state';\n\n try {\n config.onStatusChange?.('navigating');\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n await page.waitForLoadState('networkidle').catch(() => {});\n await installClickTracker(page);\n // Re-suppress widget after recovery navigation\n await page.evaluate(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n }).catch(() => {});\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n config.onStatusChange?.('executing');\n } catch (recoveryErr) {\n finalResult.skipReason = `Step failed, recovery also failed: ${recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr)}`;\n }\n }\n\n stepResults.push(finalResult!);\n config.onStepComplete?.(finalResult!, i, steps.length);\n }\n\n // Generate summary\n config.onStatusChange?.('completed');\n const model = config.model ?? 'claude-sonnet-4-20250514';\n const summary = await generateRunSummary(anthropic, config.testCase.title, stepResults, model);\n\n // Determine overall result\n const overallResult = determineOverallResult(stepResults);\n\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult,\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary,\n screenshotUrls: [],\n tokenUsage: getTokenEstimate(steps.length),\n browserSessionId: session!.sessionId,\n };\n } catch (err) {\n // Fatal error — test couldn't complete\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult: 'error',\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary: `Test execution failed: ${err instanceof Error ? err.message : String(err)}`,\n screenshotUrls: [],\n tokenUsage: getTokenEstimate(stepResults.length),\n browserSessionId: session?.sessionId ?? 'unknown',\n };\n } finally {\n // Clean up event listeners to prevent memory leaks\n if (session?.page) {\n const rawPage = session.page as any;\n rawPage.removeAllListeners?.('console');\n rawPage.removeAllListeners?.('requestfailed');\n rawPage.removeAllListeners?.('response');\n }\n await session?.close();\n }\n}\n\n/**\n * Determine the overall test result from step results.\n */\nfunction determineOverallResult(steps: StepResult[]): OverallResult {\n if (steps.length === 0) return 'error';\n\n const nonSkipped = steps.filter((s) => !s.skipped);\n const skippedCount = steps.length - nonSkipped.length;\n\n // All steps were skipped — nothing actually ran\n if (nonSkipped.length === 0) return 'error';\n\n const allNonSkippedPassed = nonSkipped.every((s) => s.passed);\n const hasErrors = nonSkipped.some((s) => s.error);\n\n // Some steps skipped, all remaining passed\n if (skippedCount > 0 && allNonSkippedPassed) return 'passed_with_skips';\n\n // Clean pass (no skips)\n if (allNonSkippedPassed) return 'passed';\n\n // All non-skipped failed or had errors\n if (nonSkipped.every((s) => !s.passed) || hasErrors) return 'failed';\n\n // Mixed results among non-skipped steps\n return 'partial';\n}\n","/**\n * Browser Provider\n *\n * Uses Stagehand (by Browserbase) for AI-powered browser automation.\n * Supports both Browserbase hosted sessions and local Playwright.\n */\n\nimport { Stagehand } from '@browserbasehq/stagehand';\nimport type { Page } from '@browserbasehq/stagehand';\nimport { performSupabaseAuth } from './supabase-auth';\nimport type { BrowserConfig, AuthConfig, CapturedRequest, NetworkError } from './types';\n\nconst DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n\nexport interface StagehandSession {\n stagehand: Stagehand;\n page: Page;\n sessionId: string;\n close: () => Promise<void>;\n}\n\n/**\n * Create a Stagehand-powered browser session.\n *\n * Stagehand wraps Playwright with AI capabilities (act, extract, observe)\n * and manages Browserbase or local browser sessions.\n */\nexport async function createStagehandSession(\n config: BrowserConfig,\n anthropicApiKey: string,\n): Promise<StagehandSession> {\n const modelName = config.model ?? DEFAULT_MODEL;\n const viewport = config.viewport ?? { width: 1280, height: 720 };\n\n const stagehand = new Stagehand({\n env: config.provider === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',\n apiKey: config.provider === 'browserbase' ? config.browserbaseApiKey : undefined,\n projectId: config.provider === 'browserbase' ? config.browserbaseProjectId : undefined,\n model: {\n modelName: modelName as any,\n apiKey: anthropicApiKey,\n },\n // Bypass pino logger — its pino-pretty transport uses worker threads\n // which fail in Vercel's serverless environment\n logger: (msg) => {\n if ((msg.level ?? 0) >= 40) console.warn('[Stagehand]', msg.message);\n },\n localBrowserLaunchOptions:\n config.provider === 'local'\n ? {\n headless: config.headless ?? true,\n viewport,\n }\n : undefined,\n browserbaseSessionCreateParams:\n config.provider === 'browserbase'\n ? { projectId: config.browserbaseProjectId! }\n : undefined,\n });\n\n await stagehand.init();\n\n const page = stagehand.context.activePage()!;\n await page.setViewportSize(viewport.width, viewport.height);\n\n // Extract session ID\n let sessionId = `local-${Date.now()}`;\n if (config.provider === 'browserbase' && stagehand.browserbaseSessionID) {\n sessionId = stagehand.browserbaseSessionID;\n }\n\n return {\n stagehand,\n page,\n sessionId,\n close: async () => {\n await stagehand.close().catch(() => {});\n },\n };\n}\n\n/**\n * Suppress the BugBear widget in the browser session.\n *\n * Uses Playwright's addInitScript() on the browser context to set a suppression\n * flag before any page script runs. This prevents the widget from rendering and\n * interfering with test execution (clicking the widget instead of app UI, popups\n * covering elements, etc.).\n *\n * The flag is checked by BugBearProvider and BugBearPanel in @bbearai/react.\n */\nexport async function suppressBugBearWidget(stagehand: Stagehand): Promise<void> {\n try {\n const ctx = stagehand.context as any;\n if (ctx?.addInitScript) {\n await ctx.addInitScript(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n });\n }\n } catch {\n // addInitScript not available — fallback to page.evaluate in runner\n }\n}\n\n/**\n * Inject authentication into the browser session.\n * Uses Stagehand's Page API and CDP for cookie injection.\n */\nexport async function injectAuth(page: Page, auth: AuthConfig, stagehand?: Stagehand): Promise<void> {\n if (auth.type === 'cookie') {\n // Use CDP to set cookies (Stagehand's Page doesn't have context().addCookies)\n for (const c of auth.cookies) {\n await page.sendCDP('Network.setCookie', {\n name: c.name,\n value: c.value,\n domain: c.domain,\n path: c.path ?? '/',\n secure: c.secure ?? false,\n httpOnly: c.httpOnly ?? false,\n sameSite: c.sameSite ?? 'Lax',\n });\n }\n } else if (auth.type === 'localStorage') {\n const currentUrl = page.url();\n if (currentUrl === 'about:blank') {\n // We'll inject localStorage after navigation in the runner\n return;\n }\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, auth.items);\n } else if (auth.type === 'form-login') {\n await performFormLogin(page, auth, stagehand);\n } else if (auth.type === 'supabase-native') {\n await performSupabaseAuth(page, auth);\n }\n}\n\n// ============================================\n// Network Capture (for exploratory testing)\n// ============================================\n\nexport interface NetworkCapture {\n start: () => void;\n stop: () => void;\n getRequests: () => CapturedRequest[];\n getErrors: () => NetworkError[];\n}\n\n/**\n * Create a per-action network capture for a Stagehand Page.\n *\n * Captures all non-static network requests and categorises errors (4xx/5xx\n * responses and failed requests). The caller controls the capture window\n * via `start()` / `stop()` — typically one window per exploration action.\n *\n * NOTE: Stagehand's Page type doesn't expose Playwright's `on('response')`\n * in its type definitions, but the underlying Playwright page supports it.\n * We cast to `any` for the event-listener calls (same pattern as runner.ts).\n */\nexport function createNetworkCapture(page: Page): NetworkCapture {\n const requests: CapturedRequest[] = [];\n const errors: NetworkError[] = [];\n let active = false;\n let startTimestamp = Date.now();\n\n const onResponse = async (response: any) => {\n if (!active) return;\n const req = response.request();\n // Skip static assets\n const resourceType = typeof req.resourceType === 'function' ? req.resourceType() : req.resourceType;\n if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) return;\n\n const entry: CapturedRequest = {\n method: typeof req.method === 'function' ? req.method() : String(req.method),\n url: (typeof response.url === 'function' ? response.url() : String(response.url)).slice(0, 500),\n status: typeof response.status === 'function' ? response.status() : Number(response.status),\n timestamp: new Date().toISOString(),\n };\n\n // Capture response body for non-2xx API responses (truncated)\n const status = entry.status;\n if (status >= 400) {\n try {\n const body = await response.text();\n entry.responseBody = body.slice(0, 500);\n } catch {}\n errors.push({\n method: entry.method,\n url: entry.url,\n status,\n statusText: typeof response.statusText === 'function' ? response.statusText() : String(response.statusText ?? ''),\n timestamp: Date.now() - startTimestamp,\n });\n }\n\n // Capture request body for POST/PUT/PATCH\n if (['POST', 'PUT', 'PATCH'].includes(entry.method)) {\n try {\n const postData = typeof req.postData === 'function' ? req.postData() : req.postData;\n if (postData) entry.requestBody = String(postData).slice(0, 500);\n } catch {}\n }\n\n requests.push(entry);\n };\n\n const onRequestFailed = (req: any) => {\n if (!active) return;\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n errors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - startTimestamp,\n });\n };\n\n const rawPage = page as any;\n\n let responseSupported = true;\n let requestFailedSupported = true;\n\n return {\n start() {\n active = true;\n requests.length = 0;\n errors.length = 0;\n startTimestamp = Date.now();\n // Stagehand may not support these Playwright events — degrade gracefully\n if (responseSupported) {\n try { rawPage.on('response', onResponse); } catch { responseSupported = false; }\n }\n if (requestFailedSupported) {\n try { rawPage.on('requestfailed', onRequestFailed); } catch { requestFailedSupported = false; }\n }\n },\n stop() {\n active = false;\n if (responseSupported) {\n try { rawPage.off('response', onResponse); } catch {}\n }\n if (requestFailedSupported) {\n try { rawPage.off('requestfailed', onRequestFailed); } catch {}\n }\n },\n getRequests: () => [...requests],\n getErrors: () => [...errors],\n };\n}\n\n/**\n * Automate login via a web form.\n *\n * Uses Stagehand's act() when available for AI-powered form filling,\n * falling back to manual locator-based login.\n */\nasync function performFormLogin(\n page: Page,\n auth: { loginUrl: string; email: string; password: string },\n stagehand?: Stagehand,\n): Promise<void> {\n await page.goto(auth.loginUrl, { waitUntil: 'domcontentloaded' });\n\n // Wait for the page to settle\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n\n // Always fill credentials via direct DOM manipulation — never send them through AI prompts\n await fillLoginCredentials(page, auth);\n\n if (stagehand) {\n // Use Stagehand AI only for the submit button click (no credentials in the prompt)\n await stagehand.act(\n 'Click the login, sign-in, or submit button to submit the form.',\n ).catch(() => {\n // Stagehand couldn't find submit — fall back to manual submit\n });\n } else {\n await clickSubmitButton(page);\n }\n\n // Wait for navigation after login\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n}\n\n/**\n * Fill login credentials using direct DOM manipulation.\n * Credentials stay local — never sent through AI prompts.\n */\nasync function fillLoginCredentials(\n page: Page,\n auth: { email: string; password: string },\n): Promise<void> {\n // Wait for a form or input to appear\n await page\n .waitForSelector(\n 'input[type=\"email\"], input[type=\"text\"][name*=\"email\"], input[name*=\"user\"], input[type=\"text\"]',\n { timeout: 15_000 },\n )\n .catch(() => {});\n\n // Try common email/username selectors in priority order\n const emailSelectors = [\n 'input[type=\"email\"]',\n 'input[name=\"email\"]',\n 'input[name=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[autocomplete=\"username\"]',\n 'input[type=\"text\"][name*=\"email\"]',\n 'input[type=\"text\"][name*=\"user\"]',\n 'input[type=\"text\"]',\n ];\n\n let emailFilled = false;\n for (const sel of emailSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.fill(auth.email);\n emailFilled = true;\n break;\n }\n }\n\n if (!emailFilled) {\n throw new Error('Could not find email/username input on login page');\n }\n\n // Fill password\n const passwordLocator = page.locator('input[type=\"password\"]');\n if ((await passwordLocator.count()) > 0 && (await passwordLocator.isVisible())) {\n await passwordLocator.fill(auth.password);\n } else {\n throw new Error('Could not find password input on login page');\n }\n}\n\n/**\n * Click the submit button using locator-based fallbacks.\n */\nasync function clickSubmitButton(page: Page): Promise<void> {\n const submitSelectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n ];\n\n let submitted = false;\n for (const sel of submitSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.click();\n submitted = true;\n break;\n }\n }\n\n if (!submitted) {\n // Fallback: press Enter on the password field\n await page.locator('input[type=\"password\"]').type('\\n');\n }\n}\n","/**\n * Supabase Native Auth\n *\n * Authenticates against Supabase GoTrue API directly, bypassing fragile\n * form-based login. Injects the session into localStorage so the app\n * picks it up on page load — no DOM interaction required.\n */\n\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { SupabaseNativeAuth } from './types';\n\n/** Supabase GoTrue session shape (subset we need) */\ninterface GoTrueSession {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n expires_at: number;\n token_type: string;\n user: {\n id: string;\n email: string;\n role: string;\n aud: string;\n };\n}\n\n/**\n * Extract the project ref from a Supabase URL.\n * e.g. \"https://abcdef.supabase.co\" → \"abcdef\"\n */\nfunction extractProjectRef(supabaseUrl: string): string {\n const url = new URL(supabaseUrl);\n const hostname = url.hostname;\n const ref = hostname.split('.')[0];\n return ref;\n}\n\n/**\n * Authenticate via Supabase GoTrue REST API and return the session.\n */\nexport async function authenticateSupabase(\n auth: SupabaseNativeAuth,\n): Promise<GoTrueSession> {\n const url = `${auth.supabaseUrl.replace(/\\/$/, '')}/auth/v1/token?grant_type=password`;\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'apikey': auth.anonKey,\n },\n body: JSON.stringify({\n email: auth.email,\n password: auth.password,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Supabase auth failed (${response.status}): ${body.slice(0, 200)}`,\n );\n }\n\n const session: GoTrueSession = await response.json();\n\n if (!session.access_token) {\n throw new Error('Supabase auth returned no access_token');\n }\n\n return session;\n}\n\n/**\n * Inject a Supabase session into the browser's localStorage.\n *\n * The app's Supabase client reads from `sb-<ref>-auth-token` on load.\n * We inject the token into localStorage so the app authenticates on\n * the next page load — no DOM interaction needed.\n */\nexport async function injectSupabaseAuth(\n page: Page,\n auth: SupabaseNativeAuth,\n session: GoTrueSession,\n): Promise<void> {\n const ref = extractProjectRef(auth.supabaseUrl);\n const storageKey = `sb-${ref}-auth-token`;\n\n const storageValue = JSON.stringify({\n access_token: session.access_token,\n refresh_token: session.refresh_token,\n expires_in: session.expires_in,\n expires_at: session.expires_at,\n token_type: session.token_type,\n user: session.user,\n });\n\n // Need an origin for localStorage — if on about:blank, navigate first\n const currentUrl = page.url();\n if (currentUrl === 'about:blank' || !currentUrl) {\n await page.goto(auth.supabaseUrl.replace(/\\/$/, ''), {\n waitUntil: 'domcontentloaded',\n timeoutMs: 10_000,\n }).catch(() => {\n // Supabase URL itself may not serve HTML — we just need an origin\n });\n }\n\n await page.evaluate(\n ({ key, value }: { key: string; value: string }) => {\n localStorage.setItem(key, value);\n },\n { key: storageKey, value: storageValue },\n );\n}\n\n/**\n * Verify the session is valid by calling the Supabase user endpoint.\n */\nexport async function verifySupabaseSession(\n auth: SupabaseNativeAuth,\n accessToken: string,\n): Promise<boolean> {\n const url = `${auth.supabaseUrl.replace(/\\/$/, '')}/auth/v1/user`;\n\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'apikey': auth.anonKey,\n },\n });\n\n return response.ok;\n}\n\n/**\n * Full Supabase auth flow: authenticate → inject → verify.\n */\nexport async function performSupabaseAuth(\n page: Page,\n auth: SupabaseNativeAuth,\n): Promise<void> {\n const session = await authenticateSupabase(auth);\n await injectSupabaseAuth(page, auth, session);\n\n const valid = await verifySupabaseSession(auth, session.access_token);\n if (!valid) {\n throw new Error('Supabase auth verification failed — session token rejected');\n }\n}\n","/**\n * Result Evaluator\n *\n * Generates AI summaries of test run results.\n * Step-level evaluation is handled by vision-evaluator.ts (Claude Vision).\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\n/**\n * Generate an AI summary of the entire test run.\n */\nexport async function generateRunSummary(\n anthropic: Anthropic,\n testTitle: string,\n steps: Array<{\n stepNumber: number;\n action: string;\n expectedResult: string;\n actualResult: string;\n passed: boolean;\n confidence: number;\n error?: string;\n skipped?: boolean;\n }>,\n model: string,\n): Promise<string> {\n const stepsText = steps\n .map(\n (s) =>\n `Step ${s.stepNumber}: ${s.action}\\n Expected: ${s.expectedResult}\\n Actual: ${s.actualResult}\\n Result: ${s.skipped ? 'SKIPPED' : s.passed ? 'PASS' : 'FAIL'} (confidence: ${Math.round(s.confidence * 100)}%)${s.error ? `\\n Error: ${s.error}` : ''}`\n )\n .join('\\n\\n');\n\n const passCount = steps.filter((s) => s.passed && !s.skipped).length;\n const failCount = steps.filter((s) => !s.passed && !s.skipped).length;\n const skipCount = steps.filter((s) => s.skipped).length;\n\n const skipNote = skipCount > 0\n ? ' Some steps were skipped due to page state recovery — these are not failures, just steps that could not be executed.'\n : '';\n\n const response = await anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: `Summarize this AI test execution in 2-3 sentences. Focus on what was tested, what passed, and what failed (if anything).${skipNote} Be concise and factual.\n\nTest: ${testTitle}\nResults: ${passCount} passed, ${failCount} failed, ${skipCount} skipped out of ${steps.length} steps\n\n${stepsText}`,\n },\n ],\n });\n\n return response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n}\n","/**\n * Vision-Based Step Evaluator\n *\n * Replaces Stagehand's extract() with direct Claude Messages API calls\n * using before/after screenshots. This gives Claude visual context\n * instead of just DOM text, catching visual regressions, layout shifts,\n * and rendering issues that DOM-only evaluation misses.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\nexport interface StepEvaluationInput {\n anthropic: Anthropic;\n screenshotBefore: Buffer;\n screenshotAfter: Buffer;\n action: string;\n expectedResult: string;\n /** Optional hint to guide what the evaluator should look for */\n evaluationHint?: string;\n model?: string;\n}\n\nexport interface StepEvaluation {\n passed: boolean;\n confidence: number;\n actualResult: string;\n}\n\n/**\n * Evaluate a test step by comparing before/after screenshots using Claude Vision.\n *\n * Sends both screenshots as image content blocks along with a structured\n * evaluation prompt. Returns a typed assessment with pass/fail, confidence,\n * and a description of what actually happened.\n */\nexport async function evaluateStep(input: StepEvaluationInput): Promise<StepEvaluation> {\n const model = input.model ?? DEFAULT_MODEL;\n\n const hintClause = input.evaluationHint\n ? `\\nEVALUATION HINT: ${input.evaluationHint}`\n : '';\n\n const response = await input.anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: 'BEFORE screenshot (page state before the action):',\n },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: input.screenshotBefore.toString('base64'),\n },\n },\n {\n type: 'text',\n text: 'AFTER screenshot (page state after the action):',\n },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: input.screenshotAfter.toString('base64'),\n },\n },\n {\n type: 'text',\n text: `You are a QA test evaluator. Compare the BEFORE and AFTER screenshots to evaluate this test step.\n\nACTION PERFORMED: ${input.action}\nEXPECTED RESULT: ${input.expectedResult}${hintClause}\n\nAnalyze the visual differences between the two screenshots and determine if the expected result was achieved.\n\nRespond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\n \"passed\": true/false,\n \"confidence\": 0.0-1.0,\n \"actualResult\": \"Brief description of what actually changed between the screenshots\"\n}\n\nConfidence guide:\n- 0.95-1.0: Clearly achieved/not achieved, obvious visual evidence\n- 0.8-0.94: Very likely, strong visual indicators\n- 0.6-0.79: Probable but some ambiguity\n- Below 0.6: Uncertain, hard to tell from screenshots alone`,\n },\n ],\n },\n ],\n });\n\n const text = response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n\n return parseEvaluation(text);\n}\n\n/**\n * Parse the evaluation response from Claude.\n * Handles JSON extraction from potentially wrapped responses.\n */\nfunction parseEvaluation(text: string): StepEvaluation {\n // Try direct JSON parse first\n try {\n const parsed = JSON.parse(text.trim());\n return validateEvaluation(parsed);\n } catch {\n // Try to extract JSON from markdown code blocks or surrounding text\n const jsonMatch = text.match(/\\{[\\s\\S]*\"passed\"[\\s\\S]*\"confidence\"[\\s\\S]*\"actualResult\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n return validateEvaluation(parsed);\n } catch {\n // Fall through to default\n }\n }\n }\n\n // Fallback: couldn't parse response\n return {\n passed: false,\n confidence: 0.3,\n actualResult: `Vision evaluation returned unparseable response: ${text.slice(0, 200)}`,\n };\n}\n\n/**\n * Validate and normalize the parsed evaluation object.\n */\nfunction validateEvaluation(parsed: Record<string, unknown>): StepEvaluation {\n return {\n passed: typeof parsed.passed === 'boolean' ? parsed.passed : false,\n confidence: typeof parsed.confidence === 'number'\n ? Math.max(0, Math.min(1, parsed.confidence))\n : 0.5,\n actualResult: typeof parsed.actualResult === 'string'\n ? parsed.actualResult\n : 'No description provided',\n };\n}\n","/**\n * Action Executor\n *\n * Executes test step actions using a tiered approach:\n * 1. If step has selector + actionType → Playwright direct (deterministic, fast)\n * 2. If step has only natural language → Stagehand AI fallback\n * 3. If Playwright action fails → Fall back to Stagehand with the natural language\n *\n * This eliminates AI flakiness for steps that have been enriched with\n * selectors while preserving AI flexibility for natural-language-only steps.\n */\n\nimport type { Stagehand, Page } from '@browserbasehq/stagehand';\nimport type { TestStep } from './types';\n\nexport interface ActionResult {\n /** Whether the action was executed via Playwright (true) or Stagehand AI (false) */\n deterministic: boolean;\n /** Error message if action failed */\n error?: string;\n}\n\n/**\n * Execute a test step action, preferring deterministic Playwright\n * when the step has a selector, falling back to Stagehand AI otherwise.\n */\nexport async function executeAction(\n page: Page,\n stagehand: Stagehand,\n step: TestStep,\n): Promise<ActionResult> {\n // Path 1: Deterministic Playwright execution\n if (step.selector && step.actionType) {\n try {\n await executePlaywrightAction(page, step);\n return { deterministic: true };\n } catch (err) {\n // Playwright failed — fall back to Stagehand AI with natural language\n const fallbackResult = await executeStagehandAction(stagehand, step);\n return {\n deterministic: false,\n error: fallbackResult.error\n ? `Playwright failed (${err instanceof Error ? err.message : String(err)}), Stagehand fallback also failed: ${fallbackResult.error}`\n : undefined,\n };\n }\n }\n\n // Path 2: Stagehand AI (natural language only)\n return executeStagehandAction(stagehand, step);\n}\n\n/**\n * Execute a step using Playwright's locator API directly.\n * No AI involved — deterministic and fast.\n */\nasync function executePlaywrightAction(page: Page, step: TestStep): Promise<void> {\n const { actionType, selector, value, waitMs } = step;\n\n // Stagehand's Page/Locator API is a subset of Playwright's —\n // methods like click(), fill() don't accept option objects.\n switch (actionType) {\n case 'click': {\n const locator = page.locator(selector!);\n await locator.click();\n break;\n }\n case 'fill': {\n const locator = page.locator(selector!);\n await locator.fill(value ?? '');\n break;\n }\n case 'select': {\n // Stagehand's locator doesn't expose selectOption — use evaluate\n await page.evaluate(\n ({ sel, val }: { sel: string; val: string }) => {\n const el = document.querySelector(sel) as HTMLSelectElement | null;\n if (!el) throw new Error(`Select element not found: ${sel}`);\n el.value = val;\n el.dispatchEvent(new Event('change', { bubbles: true }));\n },\n { sel: selector!, val: value ?? '' },\n );\n break;\n }\n case 'navigate': {\n const url = value ?? selector ?? '';\n if (!url) throw new Error('Navigate action requires a value or selector with the URL');\n await page.goto(url, { waitUntil: 'domcontentloaded', timeoutMs: 15_000 });\n break;\n }\n case 'scroll': {\n // Use evaluate for scroll — Stagehand's Locator doesn't expose scrollIntoViewIfNeeded\n await page.evaluate((sel: string) => {\n const el = document.querySelector(sel);\n if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }, selector!);\n break;\n }\n case 'wait': {\n if (selector) {\n await page.waitForSelector(selector, { timeout: waitMs ?? 10_000 });\n } else if (waitMs) {\n await page.waitForTimeout(waitMs);\n }\n break;\n }\n case 'assert': {\n // Assert steps don't perform browser actions — evaluation is handled\n // by the vision evaluator after the screenshot comparison.\n break;\n }\n default: {\n throw new Error(`Unknown actionType: ${actionType}`);\n }\n }\n\n // Apply explicit post-action wait if specified\n if (waitMs && actionType !== 'wait') {\n await page.waitForTimeout(waitMs);\n }\n}\n\n/**\n * Execute a step using Stagehand's AI-powered act().\n * Used as the primary path for unenriched steps and as a fallback.\n */\nasync function executeStagehandAction(\n stagehand: Stagehand,\n step: TestStep,\n): Promise<ActionResult> {\n try {\n await stagehand.act(step.action);\n return { deterministic: false };\n } catch (err) {\n return {\n deterministic: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","/**\n * Selector Discovery\n *\n * After Stagehand successfully executes a natural-language step,\n * attempts to discover which element was interacted with. Records\n * the best available selector so the test case can be enriched\n * for deterministic execution next time.\n *\n * Discovery data is stored in ai_step_results.actions_taken (JSONB).\n */\n\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { StepActionType } from './types';\n\nexport interface DiscoveredSelector {\n /** The selector that was discovered */\n selector: string;\n /** How the selector was derived */\n strategy: 'data-testid' | 'role' | 'aria-label' | 'id' | 'css-path';\n /** Suggested actionType based on the element */\n suggestedActionType?: StepActionType;\n /** Element tag name */\n tagName: string;\n /** Visible text content (truncated) */\n textContent?: string;\n}\n\n/**\n * Attempt to discover the selector for the last-interacted element.\n *\n * Uses page.evaluate() to find the currently focused or last-clicked\n * element and extract the best available selector for it.\n *\n * Returns null if no element can be identified.\n */\nexport async function discoverSelector(page: Page): Promise<DiscoveredSelector | null> {\n try {\n const result = await page.evaluate(() => {\n // Try to find the last-interacted element\n // Priority: activeElement (focused), then last-clicked via __bbLastClicked\n const el = (document as any).__bbLastClicked ?? document.activeElement;\n if (!el || el === document.body || el === document.documentElement) return null;\n\n const tagName = el.tagName?.toLowerCase() ?? 'unknown';\n const textContent = (el.textContent ?? '').trim().slice(0, 100);\n\n // Build selector by priority\n let selector = '';\n let strategy = 'css-path';\n\n // 1. data-testid (most stable)\n const testId = el.getAttribute('data-testid') ?? el.getAttribute('data-test-id');\n if (testId) {\n selector = `[data-testid=\"${testId}\"]`;\n strategy = 'data-testid';\n }\n // 2. id (stable if not auto-generated)\n else if (el.id && !/^:r[0-9a-z]+:?$/.test(el.id) && !/^react-/.test(el.id)) {\n selector = `#${el.id}`;\n strategy = 'id';\n }\n // 3. role + name (accessible)\n else if (el.getAttribute('role')) {\n const role = el.getAttribute('role');\n const name = el.getAttribute('aria-label') ?? el.getAttribute('name') ?? '';\n if (name) {\n selector = `[role=\"${role}\"][aria-label=\"${name}\"]`;\n strategy = 'role';\n } else {\n selector = `[role=\"${role}\"]`;\n strategy = 'role';\n }\n }\n // 4. aria-label\n else if (el.getAttribute('aria-label')) {\n selector = `[aria-label=\"${el.getAttribute('aria-label')}\"]`;\n strategy = 'aria-label';\n }\n // 5. CSS path (least stable, last resort)\n else {\n const parts: string[] = [];\n let current = el;\n while (current && current !== document.body) {\n let part = current.tagName.toLowerCase();\n if (current.className && typeof current.className === 'string') {\n // Use first non-utility class\n const classes = current.className.split(/\\s+/).filter(\n (c: string) => c && !c.startsWith('_') && c.length < 30\n );\n if (classes.length > 0) {\n part += `.${classes[0]}`;\n }\n }\n parts.unshift(part);\n current = current.parentElement;\n if (parts.length >= 4) break; // Don't go too deep\n }\n selector = parts.join(' > ');\n strategy = 'css-path';\n }\n\n // Suggest actionType based on element\n let suggestedActionType: string | undefined;\n if (tagName === 'button' || tagName === 'a' || el.getAttribute('role') === 'button') {\n suggestedActionType = 'click';\n } else if (tagName === 'input' || tagName === 'textarea') {\n const type = el.getAttribute('type') ?? 'text';\n if (type === 'checkbox' || type === 'radio') {\n suggestedActionType = 'click';\n } else {\n suggestedActionType = 'fill';\n }\n } else if (tagName === 'select') {\n suggestedActionType = 'select';\n }\n\n return { selector, strategy, suggestedActionType, tagName, textContent };\n });\n\n return result as DiscoveredSelector | null;\n } catch {\n // Discovery is best-effort — don't fail the test\n return null;\n }\n}\n\n/**\n * Install a click tracker on the page.\n *\n * Records the last-clicked element in `document.__bbLastClicked`\n * so discoverSelector() can find it after Stagehand's act().\n * Should be called once after page navigation.\n */\nexport async function installClickTracker(page: Page): Promise<void> {\n try {\n await page.evaluate(() => {\n document.addEventListener('click', (e) => {\n (document as any).__bbLastClicked = e.target;\n }, { capture: true });\n });\n } catch {\n // Best-effort — Stagehand may not support this\n }\n}\n","/**\n * AI Test Execution Cost Estimation\n *\n * Provides pre-run cost estimates and post-run cost calculations\n * based on per-model token pricing and calibrated usage profiles.\n */\n\n// Per-1M token pricing (USD) — updated Feb 2026\nconst MODEL_PRICING: Record<string, { input: number; output: number }> = {\n 'claude-sonnet-4-20250514': { input: 3.0, output: 15.0 },\n 'claude-haiku-4-20250514': { input: 0.80, output: 4.0 },\n 'claude-opus-4-20250514': { input: 15.0, output: 75.0 },\n // Aliases\n 'sonnet': { input: 3.0, output: 15.0 },\n 'haiku': { input: 0.80, output: 4.0 },\n 'opus': { input: 15.0, output: 75.0 },\n};\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\n// Calibrated token usage per operation (from real Stagehand runs)\nconst TOKEN_PROFILE = {\n /** act() — screenshot + DOM context → action decision */\n actInput: 2000,\n actOutput: 200,\n /** extract() — screenshot + extraction schema → structured result */\n extractInput: 3000,\n extractOutput: 500,\n /** summary — all step results → narrative summary (once per run) */\n summaryInput: 2000,\n summaryOutput: 500,\n};\n\nexport interface CostEstimate {\n /** Cost in cents (USD) */\n cents: number;\n /** Formatted string (e.g., \"$0.12\") */\n formatted: string;\n /** Token breakdown */\n tokens: {\n inputTokens: number;\n outputTokens: number;\n };\n /** Model used for estimate */\n model: string;\n}\n\n/**\n * Calculate actual cost from known token counts.\n */\nexport function estimateCost(\n inputTokens: number,\n outputTokens: number,\n model?: string,\n): CostEstimate {\n const resolvedModel = model ?? DEFAULT_MODEL;\n const pricing = MODEL_PRICING[resolvedModel] ?? MODEL_PRICING[DEFAULT_MODEL];\n\n const inputCost = (inputTokens / 1_000_000) * pricing.input;\n const outputCost = (outputTokens / 1_000_000) * pricing.output;\n const totalDollars = inputCost + outputCost;\n const cents = Math.round(totalDollars * 100 * 100) / 100; // 2 decimal cents\n\n return {\n cents,\n formatted: `$${totalDollars.toFixed(4)}`,\n tokens: { inputTokens, outputTokens },\n model: resolvedModel,\n };\n}\n\n/**\n * Pre-run cost estimate based on step count.\n * Each step involves: act() + extract(). Plus one summary at the end.\n */\nexport function estimateTestCost(stepCount: number, model?: string): CostEstimate {\n const inputTokens =\n stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput;\n const outputTokens =\n stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput;\n\n return estimateCost(inputTokens, outputTokens, model);\n}\n\n/**\n * Estimate cost for a batch of test cases.\n */\nexport function estimateBatchCost(\n testCases: Array<{ stepCount: number }>,\n model?: string,\n): CostEstimate {\n let totalInput = 0;\n let totalOutput = 0;\n\n for (const tc of testCases) {\n totalInput +=\n tc.stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput;\n totalOutput +=\n tc.stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput;\n }\n\n return estimateCost(totalInput, totalOutput, model);\n}\n\n/**\n * Get calibrated token estimates for a test with N steps.\n * More accurate than the old hardcoded 3000/500 per step.\n */\nexport function getTokenEstimate(stepCount: number): {\n inputTokens: number;\n outputTokens: number;\n} {\n return {\n inputTokens:\n stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput,\n outputTokens:\n stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput,\n };\n}\n","/**\n * Exploratory Testing Runner\n *\n * Implements the observe->act->evaluate loop for autonomous\n * feature exploration. The AI navigates a feature area,\n * tries edge cases, and reports findings.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport {\n ExplorationConfig,\n ExplorationAction,\n ExplorationResult,\n FindingCategory,\n FindingSeverity,\n ConsoleEntry,\n} from './types';\nimport { createStagehandSession, injectAuth, createNetworkCapture, suppressBugBearWidget } from './browser';\n\nconst DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n\n/** Default timeout for AI operations (60 seconds - longer for exploration) */\nconst AI_OPERATION_TIMEOUT_MS = 60_000;\n\n/** Wrap a promise with a timeout */\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`${operation} timed out after ${timeoutMs}ms`)), timeoutMs);\n });\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n}\n\nexport async function runExploration(config: ExplorationConfig): Promise<ExplorationResult> {\n const {\n targetUrl,\n featureDescription,\n actionBudget,\n auth,\n browserConfig,\n anthropicApiKey,\n model = DEFAULT_MODEL,\n onActionComplete,\n } = config;\n\n const anthropic = new Anthropic({ apiKey: anthropicApiKey });\n const startTime = Date.now();\n const actions: ExplorationAction[] = [];\n let totalInputTokens = 0;\n let totalOutputTokens = 0;\n\n const session = await createStagehandSession(browserConfig, anthropicApiKey);\n const { stagehand, page } = session;\n\n // Suppress BugBear widget during exploration\n await suppressBugBearWidget(stagehand);\n\n try {\n await page.goto(targetUrl, { waitUntil: 'networkidle', timeoutMs: 30_000 });\n\n if (auth) {\n await injectAuth(page, auth, stagehand);\n await page.waitForLoadState('networkidle').catch(() => {});\n }\n\n const networkCapture = createNetworkCapture(page);\n\n // Console log capture (per-action)\n let consoleLogs: ConsoleEntry[] = [];\n let actionStartTime = Date.now();\n const rawPage = page as any;\n rawPage.on('console', (msg: any) => {\n const level = msg.type?.() ?? msg.type ?? 'log';\n if (['error', 'warning', 'warn'].includes(level)) {\n consoleLogs.push({\n level: level === 'warn' ? 'warning' : level as ConsoleEntry['level'],\n text: (typeof msg.text === 'function' ? msg.text() : String(msg.text ?? msg)).slice(0, 500),\n source: typeof msg.location === 'function' ? msg.location()?.url : undefined,\n timestamp: Date.now() - actionStartTime,\n });\n }\n });\n\n const actionLog: string[] = [];\n\n for (let i = 0; i < actionBudget; i++) {\n actionStartTime = Date.now();\n consoleLogs = [];\n\n // Phase 1: Observe\n const observations = await withTimeout(\n stagehand.observe(),\n AI_OPERATION_TIMEOUT_MS,\n 'Page observation',\n );\n\n // Phase 2: Decide\n const decisionResponse = await withTimeout(\n anthropic.messages.create({\n model: model.replace('anthropic/', ''),\n max_tokens: 300,\n system: buildDecisionPrompt(featureDescription, actionBudget - i, actionLog),\n messages: [\n {\n role: 'user',\n content: `Current page URL: ${page.url()}\\n\\nVisible interactive elements:\\n${formatObservations(observations)}\\n\\nWhat single action should I perform next?`,\n },\n ],\n }),\n AI_OPERATION_TIMEOUT_MS,\n 'Action decision',\n );\n\n const actionText = extractText(decisionResponse);\n totalInputTokens += decisionResponse.usage.input_tokens;\n totalOutputTokens += decisionResponse.usage.output_tokens;\n\n if (actionText.toLowerCase().includes('[done]') || actionText.toLowerCase().includes('no more actions')) {\n break;\n }\n\n // Phase 3: Capture before state\n const screenshotBefore = await page.screenshot({ type: 'png' });\n networkCapture.start();\n\n // Phase 4: Act\n try {\n await stagehand.act(actionText);\n } catch (actError) {\n networkCapture.stop();\n const screenshotAfter = await page.screenshot({ type: 'png' });\n\n const action: ExplorationAction = {\n actionNumber: i + 1,\n action: actionText,\n category: 'broken_interaction',\n severity: 'medium',\n confidence: 0.9,\n description: `Action failed: ${actError instanceof Error ? actError.message : String(actError)}`,\n screenshotBefore,\n screenshotAfter,\n networkRequests: networkCapture.getRequests(),\n consoleLogs: [...consoleLogs],\n durationMs: Date.now() - actionStartTime,\n };\n actions.push(action);\n actionLog.push(`[${i + 1}] ${actionText} -> FAILED: ${action.description}`);\n onActionComplete?.(action, i);\n continue;\n }\n\n // Phase 5: Wait for page to settle\n await page.waitForLoadState('networkidle').catch(() => {});\n await page.waitForTimeout(500);\n networkCapture.stop();\n\n // Phase 6: Capture after state\n const screenshotAfter = await page.screenshot({ type: 'png' });\n const capturedRequests = networkCapture.getRequests();\n const networkErrors = networkCapture.getErrors();\n\n // Phase 7: Evaluate\n const evalResponse = await withTimeout(\n anthropic.messages.create({\n model: model.replace('anthropic/', ''),\n max_tokens: 400,\n system: buildEvaluationPrompt(),\n messages: [\n {\n role: 'user',\n content: buildEvaluationContext(actionText, consoleLogs, networkErrors, page.url()),\n },\n ],\n }),\n AI_OPERATION_TIMEOUT_MS,\n 'Action evaluation',\n );\n\n totalInputTokens += evalResponse.usage.input_tokens;\n totalOutputTokens += evalResponse.usage.output_tokens;\n\n const evaluation = parseEvaluation(extractText(evalResponse));\n\n // Phase 8: Record\n const action: ExplorationAction = {\n actionNumber: i + 1,\n action: actionText,\n category: evaluation.category,\n severity: evaluation.severity,\n confidence: evaluation.confidence,\n description: evaluation.description,\n screenshotBefore,\n screenshotAfter,\n networkRequests: capturedRequests,\n consoleLogs: [...consoleLogs],\n domContext: evaluation.domContext,\n durationMs: Date.now() - actionStartTime,\n };\n\n actions.push(action);\n const logEntry = evaluation.category === 'normal'\n ? `[${i + 1}] ${actionText} -> OK`\n : `[${i + 1}] ${actionText} -> FINDING (${evaluation.category}): ${evaluation.description}`;\n actionLog.push(logEntry);\n onActionComplete?.(action, i);\n }\n\n // Build report (Task 5 — import dynamically since it may not exist yet during development)\n const { generateExplorationReport } = await import('./report-generator');\n const report = await generateExplorationReport(anthropic, {\n projectName: '',\n featureDescription,\n targetUrl,\n actions,\n model: model.replace('anthropic/', ''),\n });\n\n totalInputTokens += report.tokenUsage.inputTokens;\n totalOutputTokens += report.tokenUsage.outputTokens;\n\n const findings = actions.filter((a) => a.category !== 'normal');\n\n return {\n overallResult: findings.length > 0 ? 'findings' : 'clean',\n actions,\n report: report.report,\n totalDurationMs: Date.now() - startTime,\n tokenUsage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },\n browserSessionId: session.sessionId,\n };\n } catch (error) {\n return {\n overallResult: 'error',\n actions,\n report: {\n projectName: '',\n featureDescription,\n targetUrl,\n exploredAt: new Date().toISOString(),\n duration: `${Math.round((Date.now() - startTime) / 1000)}s`,\n actionsUsed: actions.length,\n actionBudget,\n findings: [],\n tested: [],\n notTested: [{ description: 'Exploration aborted due to error', reason: String(error) }],\n summary: `Exploration failed after ${actions.length} actions: ${error instanceof Error ? error.message : String(error)}`,\n suggestedPrompt: '',\n },\n totalDurationMs: Date.now() - startTime,\n tokenUsage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },\n browserSessionId: session.sessionId,\n };\n } finally {\n // Clean up event listeners to prevent memory leaks\n if (session.page) {\n const rawPage = session.page as any;\n rawPage.removeAllListeners?.('console');\n }\n await session.close();\n }\n}\n\n// --- Prompt Builders --------------------------------------------------------\n\nfunction buildDecisionPrompt(featureDescription: string, remainingBudget: number, actionLog: string[]): string {\n return `You are an exploratory QA tester examining the feature: \"${featureDescription}\".\nYour goal is to find bugs by interacting with the page like a real user would.\n\nStrategy for choosing your next action:\n1. Try the happy path first (normal usage)\n2. Then try edge cases: empty inputs, very long text, special characters\n3. Click buttons and links to verify they work\n4. Submit forms with missing required fields\n5. Look for visual problems: overlapping text, broken layouts, missing images\n\nYou have ${remainingBudget} actions left. Prioritize high-risk interactions.\n${actionLog.length > 0 ? `\\nActions already taken:\\n${actionLog.join('\\n')}` : ''}\n\nDO NOT repeat an action you've already performed.\nRespond with a single action description. If there's nothing left to test, respond with \"[DONE]\".`;\n}\n\nfunction buildEvaluationPrompt(): string {\n return `You are evaluating the result of a QA test action. Categorize what happened.\n\nRespond in this exact JSON format:\n{\n \"category\": \"normal\" | \"console_error\" | \"broken_interaction\" | \"visual_anomaly\" | \"input_handling\",\n \"severity\": \"critical\" | \"high\" | \"medium\" | \"low\",\n \"confidence\": 0.0-1.0,\n \"description\": \"What happened\",\n \"expectedBehavior\": \"What should have happened\",\n \"domSelector\": \"CSS selector of the element involved (if applicable)\"\n}\n\nCategory definitions:\n- normal: Expected behavior, no issues found\n- console_error: JavaScript exception or failed network request (4xx/5xx)\n- broken_interaction: Action had no visible effect, button didn't respond, navigation failed\n- visual_anomaly: Layout break, text overflow, missing/broken images, overlapping elements\n- input_handling: Missing validation, accepted clearly invalid input, no error feedback\n\nOnly report genuine issues. If behavior seems correct, use \"normal\".\nFor \"normal\" results, severity and domSelector are not required.`;\n}\n\nfunction buildEvaluationContext(\n action: string,\n consoleLogs: ConsoleEntry[],\n networkErrors: { method: string; url: string; status: number; statusText: string }[],\n currentUrl: string\n): string {\n let context = `Action performed: \"${action}\"\\nCurrent URL: ${currentUrl}\\n`;\n\n if (consoleLogs.length > 0) {\n context += `\\nConsole output:\\n${consoleLogs.map((l) => `[${l.level}] ${l.text}`).join('\\n')}\\n`;\n }\n\n if (networkErrors.length > 0) {\n context += `\\nFailed network requests:\\n${networkErrors.map((e) => `${e.method} ${e.url} -> ${e.status} ${e.statusText}`).join('\\n')}\\n`;\n }\n\n return context;\n}\n\n// --- Helpers ----------------------------------------------------------------\n\nfunction formatObservations(observations: { description: string; selector: string }[]): string {\n return observations\n .slice(0, 30)\n .map((o, i) => `${i + 1}. [${o.selector}] ${o.description}`)\n .join('\\n');\n}\n\nfunction extractText(response: Anthropic.Message): string {\n const block = response.content[0];\n return block.type === 'text' ? block.text : '';\n}\n\ninterface EvaluationResult {\n category: FindingCategory | 'normal';\n severity?: FindingSeverity;\n confidence: number;\n description: string;\n expectedBehavior?: string;\n domContext?: { selector: string; elementText: string; nearbyText: string };\n}\n\nfunction parseEvaluation(text: string): EvaluationResult {\n try {\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) throw new Error('No JSON found');\n const parsed = JSON.parse(jsonMatch[0]);\n return {\n category: parsed.category || 'normal',\n severity: parsed.severity,\n confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0.5,\n description: parsed.description || text,\n expectedBehavior: parsed.expectedBehavior,\n domContext: parsed.domSelector\n ? { selector: parsed.domSelector, elementText: '', nearbyText: '' }\n : undefined,\n };\n } catch {\n return { category: 'normal', confidence: 0.3, description: text };\n }\n}\n","/**\n * Report Auto-Triager\n *\n * Uses Claude to analyze incoming bug reports and auto-assign:\n * - Severity (critical/high/medium/low)\n * - Category (ui_ux/functional/crash/security/other)\n * - Duplicate detection against recent reports\n * - Root cause analysis\n *\n * Results are stored in reports.ai_analysis (JSONB).\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\n// ─── Types ──────────────────────────────────────────────────────────\n\nexport interface TriageReportInput {\n title?: string | null;\n description: string;\n app_context?: Record<string, unknown> | null;\n enhanced_context?: Record<string, unknown> | null;\n device_info?: Record<string, unknown> | null;\n navigation_history?: unknown[] | null;\n screenshot_urls?: string[] | null;\n error_fingerprint?: string | null;\n report_source?: string | null;\n}\n\nexport interface RecentReportSummary {\n id: string;\n title?: string | null;\n description: string;\n error_fingerprint?: string | null;\n severity?: string | null;\n category?: string | null;\n status: string;\n}\n\nexport interface TriageInput {\n anthropic: Anthropic;\n report: TriageReportInput;\n recentReports: RecentReportSummary[];\n model?: string;\n}\n\nexport type TriageSeverity = 'critical' | 'high' | 'medium' | 'low';\nexport type TriageCategory = 'ui_ux' | 'functional' | 'crash' | 'security' | 'other';\n\nexport interface TriageResult {\n suggested_severity: TriageSeverity;\n severity_confidence: number;\n suggested_category: TriageCategory;\n category_confidence: number;\n root_cause_analysis: string;\n duplicate_of: string | null;\n duplicate_confidence: number;\n triage_notes: string;\n}\n\n// ─── Core Logic ─────────────────────────────────────────────────────\n\n/**\n * Analyze a report using Claude and return triage suggestions.\n */\nexport async function triageReport(input: TriageInput): Promise<TriageResult> {\n const model = input.model ?? DEFAULT_MODEL;\n const { report, recentReports } = input;\n\n const prompt = buildTriagePrompt(report, recentReports);\n\n const response = await input.anthropic.messages.create({\n model,\n max_tokens: 1024,\n messages: [{ role: 'user', content: prompt }],\n });\n\n const text = response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n\n return parseTriageResult(text);\n}\n\n// ─── Prompt Construction ────────────────────────────────────────────\n\nfunction buildTriagePrompt(\n report: TriageReportInput,\n recentReports: RecentReportSummary[],\n): string {\n const sections: string[] = [];\n\n // Report core info\n sections.push(`REPORT TITLE: ${report.title ?? '(no title)'}`);\n sections.push(`DESCRIPTION: ${report.description}`);\n if (report.report_source) {\n sections.push(`SOURCE: ${report.report_source}`);\n }\n\n // App context\n if (report.app_context && Object.keys(report.app_context).length > 0) {\n const ctx = report.app_context;\n const parts: string[] = [];\n if (ctx.currentRoute) parts.push(`Route: ${ctx.currentRoute}`);\n if (ctx.currentUrl) parts.push(`URL: ${ctx.currentUrl}`);\n if (ctx.componentName) parts.push(`Component: ${ctx.componentName}`);\n if (ctx.userAction) parts.push(`User action: ${ctx.userAction}`);\n if (parts.length > 0) {\n sections.push(`APP CONTEXT:\\n${parts.join('\\n')}`);\n }\n }\n\n // Console errors from enhanced context\n if (report.enhanced_context) {\n const enhanced = report.enhanced_context;\n const consoleLogs = enhanced.consoleLogs as Array<{ level: string; text: string }> | undefined;\n if (consoleLogs && consoleLogs.length > 0) {\n const errors = consoleLogs\n .filter((l) => l.level === 'error' || l.level === 'warning')\n .slice(0, 10)\n .map((l) => `[${l.level}] ${l.text}`)\n .join('\\n');\n if (errors) {\n sections.push(`CONSOLE ERRORS:\\n${errors}`);\n }\n }\n\n const networkErrors = enhanced.networkErrors as Array<{ method: string; url: string; status: number }> | undefined;\n if (networkErrors && networkErrors.length > 0) {\n const netErrors = networkErrors\n .slice(0, 10)\n .map((e) => `${e.method} ${e.url} → ${e.status}`)\n .join('\\n');\n sections.push(`NETWORK ERRORS:\\n${netErrors}`);\n }\n }\n\n // Device info\n if (report.device_info && Object.keys(report.device_info).length > 0) {\n const device = report.device_info;\n const parts: string[] = [];\n if (device.platform) parts.push(`Platform: ${device.platform}`);\n if (device.browser) parts.push(`Browser: ${device.browser}`);\n if (device.os) parts.push(`OS: ${device.os}`);\n if (device.screenSize) parts.push(`Screen: ${device.screenSize}`);\n if (parts.length > 0) {\n sections.push(`DEVICE:\\n${parts.join(', ')}`);\n }\n }\n\n // Error fingerprint\n if (report.error_fingerprint) {\n sections.push(`ERROR FINGERPRINT: ${report.error_fingerprint}`);\n }\n\n // Recent reports for duplicate detection\n let recentSection = '';\n if (recentReports.length > 0) {\n const recentLines = recentReports.map((r) => {\n const desc = r.description.slice(0, 150);\n const fp = r.error_fingerprint ? ` [fingerprint: ${r.error_fingerprint}]` : '';\n return `- ID: ${r.id} | \"${r.title ?? '(no title)'}\" | ${desc}${fp}`;\n });\n recentSection = `\\nRECENT REPORTS (check for duplicates):\\n${recentLines.join('\\n')}`;\n }\n\n return `You are a QA triage specialist. Analyze this bug report and provide structured triage.\n\n${sections.join('\\n\\n')}\n${recentSection}\n\nRespond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\n \"suggested_severity\": \"critical\" | \"high\" | \"medium\" | \"low\",\n \"severity_confidence\": 0.0-1.0,\n \"suggested_category\": \"ui_ux\" | \"functional\" | \"crash\" | \"security\" | \"other\",\n \"category_confidence\": 0.0-1.0,\n \"root_cause_analysis\": \"Brief analysis of the likely root cause\",\n \"duplicate_of\": null or \"uuid-of-matching-report\",\n \"duplicate_confidence\": 0.0-1.0,\n \"triage_notes\": \"Summary of triage reasoning\"\n}\n\nSeverity guide:\n- critical: App crash, data loss, security vulnerability, blocks core workflow\n- high: Major feature broken, significant UX degradation, affects many users\n- medium: Feature partially broken, workaround exists, moderate impact\n- low: Minor cosmetic issue, edge case, minimal user impact\n\nCategory guide:\n- crash: App crashes, unhandled exceptions, white screen of death\n- security: Auth bypass, data exposure, injection vulnerabilities\n- functional: Feature doesn't work as expected, logic errors, broken flows\n- ui_ux: Visual glitches, layout issues, confusing UX, accessibility problems\n- other: Performance, documentation, configuration issues\n\nDuplicate detection:\n- Compare error fingerprints first (exact match = very high confidence)\n- Then compare descriptions semantically (similar symptoms on same route/feature)\n- Only flag as duplicate if confidence ≥ 0.80`;\n}\n\n// ─── Response Parsing ───────────────────────────────────────────────\n\nconst VALID_SEVERITIES: TriageSeverity[] = ['critical', 'high', 'medium', 'low'];\nconst VALID_CATEGORIES: TriageCategory[] = ['ui_ux', 'functional', 'crash', 'security', 'other'];\n\nfunction parseTriageResult(text: string): TriageResult {\n // Try direct JSON parse\n try {\n const parsed = JSON.parse(text.trim());\n return validateTriageResult(parsed);\n } catch {\n // Try to extract JSON from wrapped response\n const jsonMatch = text.match(/\\{[\\s\\S]*\"suggested_severity\"[\\s\\S]*\"suggested_category\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n return validateTriageResult(parsed);\n } catch {\n // Fall through to default\n }\n }\n }\n\n // Fallback: couldn't parse\n return {\n suggested_severity: 'medium',\n severity_confidence: 0.3,\n suggested_category: 'other',\n category_confidence: 0.3,\n root_cause_analysis: `Triage returned unparseable response: ${text.slice(0, 200)}`,\n duplicate_of: null,\n duplicate_confidence: 0,\n triage_notes: 'Auto-triage failed to parse AI response',\n };\n}\n\nfunction validateTriageResult(parsed: Record<string, unknown>): TriageResult {\n const severity = VALID_SEVERITIES.includes(parsed.suggested_severity as TriageSeverity)\n ? (parsed.suggested_severity as TriageSeverity)\n : 'medium';\n\n const category = VALID_CATEGORIES.includes(parsed.suggested_category as TriageCategory)\n ? (parsed.suggested_category as TriageCategory)\n : 'other';\n\n return {\n suggested_severity: severity,\n severity_confidence: clampConfidence(parsed.severity_confidence),\n suggested_category: category,\n category_confidence: clampConfidence(parsed.category_confidence),\n root_cause_analysis: typeof parsed.root_cause_analysis === 'string'\n ? parsed.root_cause_analysis\n : 'No analysis provided',\n duplicate_of: typeof parsed.duplicate_of === 'string' ? parsed.duplicate_of : null,\n duplicate_confidence: clampConfidence(parsed.duplicate_confidence),\n triage_notes: typeof parsed.triage_notes === 'string'\n ? parsed.triage_notes\n : 'No notes provided',\n };\n}\n\nfunction clampConfidence(value: unknown): number {\n if (typeof value !== 'number') return 0.5;\n return Math.max(0, Math.min(1, value));\n}\n","/**\n * Failure Analyzer\n *\n * When an AI test step fails, analyzes the failure to classify it as:\n * - real_bug: Actual application defect (API error, broken feature, crash)\n * - test_maintenance: Test is stale (selector changed, page restructured)\n * - flaky: Timing issue, intermittent network failure, race condition\n * - unknown: Can't determine with sufficient confidence\n *\n * For test_maintenance failures, suggests corrected selectors/actions\n * that can be auto-applied to heal the test case.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\n// ─── Types ──────────────────────────────────────────────────────────\n\nexport type FailureClassification = 'real_bug' | 'test_maintenance' | 'ai_limitation' | 'flaky' | 'unknown';\n\n/** Run-level classification (aggregated from step-level classifications) */\nexport type RunFailureClassification = 'bug' | 'test_issue' | 'ai_limitation' | 'flaky' | 'unknown';\n\nexport interface FailureAnalysis {\n classification: FailureClassification;\n confidence: number;\n reasoning: string;\n suggested_fix?: {\n stepNumber: number;\n original_action: string;\n corrected_action?: string;\n corrected_selector?: string;\n corrected_actionType?: string;\n corrected_value?: string;\n };\n}\n\nexport interface FailureAnalysisInput {\n anthropic: Anthropic;\n step: {\n stepNumber: number;\n action: string;\n expectedResult: string;\n selector?: string;\n actionType?: string;\n value?: string;\n };\n result: {\n actualResult: string;\n error?: string;\n screenshotBefore: Buffer;\n screenshotAfter: Buffer;\n };\n discoveredSelector?: {\n selector: string;\n strategy: string;\n tagName?: string;\n textContent?: string;\n };\n consoleLogs?: Array<{ level: string; text: string }>;\n networkErrors?: Array<{ method: string; url: string; status: number; statusText: string }>;\n model?: string;\n}\n\n// ─── Core Logic ─────────────────────────────────────────────────────\n\n/**\n * Analyze a failed test step to classify the failure and suggest fixes.\n */\nexport async function analyzeFailure(input: FailureAnalysisInput): Promise<FailureAnalysis> {\n const model = input.model ?? DEFAULT_MODEL;\n const { step, result, discoveredSelector, consoleLogs, networkErrors } = input;\n\n const content: Anthropic.Messages.ContentBlockParam[] = [];\n\n // Before screenshot\n content.push({ type: 'text', text: 'BEFORE screenshot (page state before the failed action):' });\n content.push({\n type: 'image',\n source: { type: 'base64', media_type: 'image/png', data: result.screenshotBefore.toString('base64') },\n });\n\n // After screenshot\n content.push({ type: 'text', text: 'AFTER screenshot (page state after the failed action):' });\n content.push({\n type: 'image',\n source: { type: 'base64', media_type: 'image/png', data: result.screenshotAfter.toString('base64') },\n });\n\n // Build analysis prompt\n content.push({ type: 'text', text: buildFailurePrompt(step, result, discoveredSelector, consoleLogs, networkErrors) });\n\n const response = await input.anthropic.messages.create({\n model,\n max_tokens: 1024,\n messages: [{ role: 'user', content }],\n });\n\n const text = response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n\n return parseFailureAnalysis(text, step);\n}\n\n// ─── Run-Level Rollup ───────────────────────────────────────────────\n\nconst STEP_TO_RUN: Record<FailureClassification, RunFailureClassification> = {\n real_bug: 'bug',\n test_maintenance: 'test_issue',\n ai_limitation: 'ai_limitation',\n flaky: 'flaky',\n unknown: 'unknown',\n};\n\n/**\n * Roll up step-level failure classifications into a single run-level classification.\n *\n * Priority:\n * 1. ANY step = real_bug → 'bug'\n * 2. ALL steps = ai_limitation → 'ai_limitation'\n * 3. ALL steps = test_maintenance → 'test_issue'\n * 4. ALL steps = flaky → 'flaky'\n * 5. Otherwise → most common classification (mapped to run-level)\n */\nexport function rollupFailureClassification(\n stepClassifications: FailureClassification[],\n): RunFailureClassification {\n if (stepClassifications.length === 0) return 'unknown';\n\n if (stepClassifications.some((c) => c === 'real_bug')) return 'bug';\n if (stepClassifications.every((c) => c === 'ai_limitation')) return 'ai_limitation';\n if (stepClassifications.every((c) => c === 'test_maintenance')) return 'test_issue';\n if (stepClassifications.every((c) => c === 'flaky')) return 'flaky';\n\n // Most common classification\n const counts = new Map<FailureClassification, number>();\n for (const c of stepClassifications) {\n counts.set(c, (counts.get(c) ?? 0) + 1);\n }\n\n let best: FailureClassification = 'unknown';\n let bestCount = 0;\n for (const [cls, count] of counts) {\n if (count > bestCount) {\n bestCount = count;\n best = cls;\n }\n }\n\n return STEP_TO_RUN[best];\n}\n\n// ─── Prompt Construction ────────────────────────────────────────────\n\nfunction buildFailurePrompt(\n step: FailureAnalysisInput['step'],\n result: FailureAnalysisInput['result'],\n discoveredSelector?: FailureAnalysisInput['discoveredSelector'],\n consoleLogs?: Array<{ level: string; text: string }>,\n networkErrors?: Array<{ method: string; url: string; status: number; statusText: string }>,\n): string {\n const sections: string[] = [];\n\n sections.push(`FAILED STEP #${step.stepNumber}: ${step.action}`);\n sections.push(`EXPECTED: ${step.expectedResult}`);\n sections.push(`ACTUAL: ${result.actualResult}`);\n\n if (step.selector) sections.push(`SELECTOR USED: ${step.selector}`);\n if (step.actionType) sections.push(`ACTION TYPE: ${step.actionType}`);\n if (result.error) sections.push(`ERROR: ${result.error}`);\n\n if (discoveredSelector) {\n sections.push(`DISCOVERED SELECTOR (what Stagehand actually clicked): ${discoveredSelector.selector} (via ${discoveredSelector.strategy})${discoveredSelector.textContent ? ` — text: \"${discoveredSelector.textContent}\"` : ''}`);\n }\n\n if (consoleLogs && consoleLogs.length > 0) {\n const errors = consoleLogs\n .filter((l) => l.level === 'error' || l.level === 'warning')\n .slice(0, 8)\n .map((l) => `[${l.level}] ${l.text}`)\n .join('\\n');\n if (errors) sections.push(`CONSOLE ERRORS:\\n${errors}`);\n }\n\n if (networkErrors && networkErrors.length > 0) {\n const netErrors = networkErrors\n .slice(0, 8)\n .map((e) => `${e.method} ${e.url} → ${e.status} ${e.statusText}`)\n .join('\\n');\n sections.push(`NETWORK ERRORS:\\n${netErrors}`);\n }\n\n return `You are a QA failure analyst. A test step failed. Analyze the before/after screenshots and the context below to classify this failure.\n\n${sections.join('\\n\\n')}\n\nClassify into ONE of these categories:\n- **real_bug**: The application has an actual defect. Indicators: API errors (4xx/5xx), JavaScript exceptions, missing/broken UI elements that SHOULD be there, incorrect behavior, data not saving.\n- **test_maintenance**: The test is stale — the app changed but the test wasn't updated. Indicators: element moved/renamed, selector no longer matches, page restructured but app works correctly, the discovered selector differs from the test's selector.\n- **ai_limitation**: The AI executor itself could not complete this step — NOT an app bug. Indicators: already logged in so can't reach the login page, a QA/testing widget or overlay appeared and blocked the real UI, the test requires measuring something the AI can't (contrast ratios, pixel measurements), the AI landed on a completely wrong page and never reached the test target, authentication redirect prevented navigation, a popup or modal unrelated to the test blocked interaction.\n- **flaky**: Timing or intermittent issue. Indicators: timeout errors, \"element not found\" but the element IS visible in screenshots, network hiccup, race condition.\n- **unknown**: Can't determine with confidence.\n\nFor **test_maintenance** failures, suggest a corrected step (selector, action, value).\n\nRespond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\n \"classification\": \"real_bug\" | \"test_maintenance\" | \"ai_limitation\" | \"flaky\" | \"unknown\",\n \"confidence\": 0.0-1.0,\n \"reasoning\": \"Brief explanation of why this classification\",\n \"suggested_fix\": null | {\n \"corrected_action\": \"Updated natural language action (if changed)\",\n \"corrected_selector\": \"Updated CSS selector (if selector changed)\",\n \"corrected_actionType\": \"Updated action type (if changed)\",\n \"corrected_value\": \"Updated value (if changed)\"\n }\n}`;\n}\n\n// ─── Response Parsing ───────────────────────────────────────────────\n\nconst VALID_CLASSIFICATIONS: FailureClassification[] = ['real_bug', 'test_maintenance', 'ai_limitation', 'flaky', 'unknown'];\n\nfunction parseFailureAnalysis(text: string, step: FailureAnalysisInput['step']): FailureAnalysis {\n // Try direct JSON parse\n try {\n const parsed = JSON.parse(text.trim());\n return validateFailureAnalysis(parsed, step);\n } catch {\n // Try to extract JSON from wrapped response\n const jsonMatch = text.match(/\\{[\\s\\S]*\"classification\"[\\s\\S]*\"confidence\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n return validateFailureAnalysis(parsed, step);\n } catch {\n // Fall through to default\n }\n }\n }\n\n return {\n classification: 'unknown',\n confidence: 0.3,\n reasoning: `Failure analysis returned unparseable response: ${text.slice(0, 200)}`,\n };\n}\n\nfunction validateFailureAnalysis(\n parsed: Record<string, unknown>,\n step: FailureAnalysisInput['step'],\n): FailureAnalysis {\n const classification = VALID_CLASSIFICATIONS.includes(parsed.classification as FailureClassification)\n ? (parsed.classification as FailureClassification)\n : 'unknown';\n\n const result: FailureAnalysis = {\n classification,\n confidence: clampConfidence(parsed.confidence),\n reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : 'No reasoning provided',\n };\n\n // Parse suggested fix if present\n if (parsed.suggested_fix && typeof parsed.suggested_fix === 'object') {\n const fix = parsed.suggested_fix as Record<string, unknown>;\n result.suggested_fix = {\n stepNumber: step.stepNumber,\n original_action: step.action,\n corrected_action: typeof fix.corrected_action === 'string' ? fix.corrected_action : undefined,\n corrected_selector: typeof fix.corrected_selector === 'string' ? fix.corrected_selector : undefined,\n corrected_actionType: typeof fix.corrected_actionType === 'string' ? fix.corrected_actionType : undefined,\n corrected_value: typeof fix.corrected_value === 'string' ? fix.corrected_value : undefined,\n };\n }\n\n return result;\n}\n\nfunction clampConfidence(value: unknown): number {\n if (typeof value !== 'number') return 0.5;\n return Math.max(0, Math.min(1, value));\n}\n","/**\n * Simple counting semaphore for controlling concurrent browser sessions.\n *\n * Usage:\n * const sem = new Semaphore(3);\n * await sem.acquire();\n * try { ... } finally { sem.release(); }\n */\nexport class Semaphore {\n private current = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly max: number) {\n if (max < 1) throw new Error('Semaphore max must be >= 1');\n }\n\n async acquire(): Promise<void> {\n if (this.current < this.max) {\n this.current++;\n return;\n }\n return new Promise<void>((resolve) => {\n this.queue.push(resolve);\n });\n }\n\n release(): void {\n const next = this.queue.shift();\n if (next) {\n // Hand the slot directly to the next waiter\n next();\n } else {\n this.current--;\n }\n }\n\n /** Number of slots currently in use */\n get active(): number {\n return this.current;\n }\n\n /** Number of waiters in the queue */\n get waiting(): number {\n return this.queue.length;\n }\n}\n"],"mappings":";;;;;AAYA,OAAO,eAAe;;;ACLtB,SAAS,iBAAiB;;;ACuB1B,SAAS,kBAAkB,aAA6B;AACtD,QAAM,MAAM,IAAI,IAAI,WAAW;AAC/B,QAAM,WAAW,IAAI;AACrB,QAAM,MAAM,SAAS,MAAM,GAAG,EAAE,CAAC;AACjC,SAAO;AACT;AAKA,eAAsB,qBACpB,MACwB;AACxB,QAAM,MAAM,GAAG,KAAK,YAAY,QAAQ,OAAO,EAAE,CAAC;AAElD,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,UAAU,KAAK;AAAA,IACjB;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,IACjB,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACjD,UAAM,IAAI;AAAA,MACR,yBAAyB,SAAS,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,UAAyB,MAAM,SAAS,KAAK;AAEnD,MAAI,CAAC,QAAQ,cAAc;AACzB,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,SAAO;AACT;AASA,eAAsB,mBACpB,MACA,MACA,SACe;AACf,QAAM,MAAM,kBAAkB,KAAK,WAAW;AAC9C,QAAM,aAAa,MAAM,GAAG;AAE5B,QAAM,eAAe,KAAK,UAAU;AAAA,IAClC,cAAc,QAAQ;AAAA,IACtB,eAAe,QAAQ;AAAA,IACvB,YAAY,QAAQ;AAAA,IACpB,YAAY,QAAQ;AAAA,IACpB,YAAY,QAAQ;AAAA,IACpB,MAAM,QAAQ;AAAA,EAChB,CAAC;AAGD,QAAM,aAAa,KAAK,IAAI;AAC5B,MAAI,eAAe,iBAAiB,CAAC,YAAY;AAC/C,UAAM,KAAK,KAAK,KAAK,YAAY,QAAQ,OAAO,EAAE,GAAG;AAAA,MACnD,WAAW;AAAA,MACX,WAAW;AAAA,IACb,CAAC,EAAE,MAAM,MAAM;AAAA,IAEf,CAAC;AAAA,EACH;AAEA,QAAM,KAAK;AAAA,IACT,CAAC,EAAE,KAAK,MAAM,MAAsC;AAClD,mBAAa,QAAQ,KAAK,KAAK;AAAA,IACjC;AAAA,IACA,EAAE,KAAK,YAAY,OAAO,aAAa;AAAA,EACzC;AACF;AAKA,eAAsB,sBACpB,MACA,aACkB;AAClB,QAAM,MAAM,GAAG,KAAK,YAAY,QAAQ,OAAO,EAAE,CAAC;AAElD,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,SAAS;AAAA,MACP,iBAAiB,UAAU,WAAW;AAAA,MACtC,UAAU,KAAK;AAAA,IACjB;AAAA,EACF,CAAC;AAED,SAAO,SAAS;AAClB;AAKA,eAAsB,oBACpB,MACA,MACe;AACf,QAAM,UAAU,MAAM,qBAAqB,IAAI;AAC/C,QAAM,mBAAmB,MAAM,MAAM,OAAO;AAE5C,QAAM,QAAQ,MAAM,sBAAsB,MAAM,QAAQ,YAAY;AACpE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,iEAA4D;AAAA,EAC9E;AACF;;;ADzIA,IAAM,gBAAgB;AAetB,eAAsB,uBACpB,QACA,iBAC2B;AAC3B,QAAM,YAAY,OAAO,SAAS;AAClC,QAAM,WAAW,OAAO,YAAY,EAAE,OAAO,MAAM,QAAQ,IAAI;AAE/D,QAAM,YAAY,IAAI,UAAU;AAAA,IAC9B,KAAK,OAAO,aAAa,gBAAgB,gBAAgB;AAAA,IACzD,QAAQ,OAAO,aAAa,gBAAgB,OAAO,oBAAoB;AAAA,IACvE,WAAW,OAAO,aAAa,gBAAgB,OAAO,uBAAuB;AAAA,IAC7E,OAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,IACV;AAAA;AAAA;AAAA,IAGA,QAAQ,CAAC,QAAQ;AACf,WAAK,IAAI,SAAS,MAAM,GAAI,SAAQ,KAAK,eAAe,IAAI,OAAO;AAAA,IACrE;AAAA,IACA,2BACE,OAAO,aAAa,UAChB;AAAA,MACE,UAAU,OAAO,YAAY;AAAA,MAC7B;AAAA,IACF,IACA;AAAA,IACN,gCACE,OAAO,aAAa,gBAChB,EAAE,WAAW,OAAO,qBAAsB,IAC1C;AAAA,EACR,CAAC;AAED,QAAM,UAAU,KAAK;AAErB,QAAM,OAAO,UAAU,QAAQ,WAAW;AAC1C,QAAM,KAAK,gBAAgB,SAAS,OAAO,SAAS,MAAM;AAG1D,MAAI,YAAY,SAAS,KAAK,IAAI,CAAC;AACnC,MAAI,OAAO,aAAa,iBAAiB,UAAU,sBAAsB;AACvE,gBAAY,UAAU;AAAA,EACxB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,YAAY;AACjB,YAAM,UAAU,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxC;AAAA,EACF;AACF;AAYA,eAAsB,sBAAsB,WAAqC;AAC/E,MAAI;AACF,UAAM,MAAM,UAAU;AACtB,QAAI,KAAK,eAAe;AACtB,YAAM,IAAI,cAAc,MAAM;AAC5B,QAAC,OAAe,qBAAqB;AACrC,YAAI;AAAE,uBAAa,QAAQ,sBAAsB,MAAM;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MACrE,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAMA,eAAsB,WAAW,MAAY,MAAkB,WAAsC;AACnG,MAAI,KAAK,SAAS,UAAU;AAE1B,eAAW,KAAK,KAAK,SAAS;AAC5B,YAAM,KAAK,QAAQ,qBAAqB;AAAA,QACtC,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,QAAQ,EAAE;AAAA,QACV,MAAM,EAAE,QAAQ;AAAA,QAChB,QAAQ,EAAE,UAAU;AAAA,QACpB,UAAU,EAAE,YAAY;AAAA,QACxB,UAAU,EAAE,YAAY;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF,WAAW,KAAK,SAAS,gBAAgB;AACvC,UAAM,aAAa,KAAK,IAAI;AAC5B,QAAI,eAAe,eAAe;AAEhC;AAAA,IACF;AACA,UAAM,KAAK,SAAS,CAAC,UAAkC;AACrD,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,qBAAa,QAAQ,KAAK,KAAK;AAAA,MACjC;AAAA,IACF,GAAG,KAAK,KAAK;AAAA,EACf,WAAW,KAAK,SAAS,cAAc;AACrC,UAAM,iBAAiB,MAAM,MAAM,SAAS;AAAA,EAC9C,WAAW,KAAK,SAAS,mBAAmB;AAC1C,UAAM,oBAAoB,MAAM,IAAI;AAAA,EACtC;AACF;AAwBO,SAAS,qBAAqB,MAA4B;AAC/D,QAAM,WAA8B,CAAC;AACrC,QAAM,SAAyB,CAAC;AAChC,MAAI,SAAS;AACb,MAAI,iBAAiB,KAAK,IAAI;AAE9B,QAAM,aAAa,OAAO,aAAkB;AAC1C,QAAI,CAAC,OAAQ;AACb,UAAM,MAAM,SAAS,QAAQ;AAE7B,UAAM,eAAe,OAAO,IAAI,iBAAiB,aAAa,IAAI,aAAa,IAAI,IAAI;AACvF,QAAI,CAAC,SAAS,cAAc,QAAQ,OAAO,EAAE,SAAS,YAAY,EAAG;AAErE,UAAM,QAAyB;AAAA,MAC7B,QAAQ,OAAO,IAAI,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO,IAAI,MAAM;AAAA,MAC3E,MAAM,OAAO,SAAS,QAAQ,aAAa,SAAS,IAAI,IAAI,OAAO,SAAS,GAAG,GAAG,MAAM,GAAG,GAAG;AAAA,MAC9F,QAAQ,OAAO,SAAS,WAAW,aAAa,SAAS,OAAO,IAAI,OAAO,SAAS,MAAM;AAAA,MAC1F,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAGA,UAAM,SAAS,MAAM;AACrB,QAAI,UAAU,KAAK;AACjB,UAAI;AACF,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAM,eAAe,KAAK,MAAM,GAAG,GAAG;AAAA,MACxC,QAAQ;AAAA,MAAC;AACT,aAAO,KAAK;AAAA,QACV,QAAQ,MAAM;AAAA,QACd,KAAK,MAAM;AAAA,QACX;AAAA,QACA,YAAY,OAAO,SAAS,eAAe,aAAa,SAAS,WAAW,IAAI,OAAO,SAAS,cAAc,EAAE;AAAA,QAChH,WAAW,KAAK,IAAI,IAAI;AAAA,MAC1B,CAAC;AAAA,IACH;AAGA,QAAI,CAAC,QAAQ,OAAO,OAAO,EAAE,SAAS,MAAM,MAAM,GAAG;AACnD,UAAI;AACF,cAAM,WAAW,OAAO,IAAI,aAAa,aAAa,IAAI,SAAS,IAAI,IAAI;AAC3E,YAAI,SAAU,OAAM,cAAc,OAAO,QAAQ,EAAE,MAAM,GAAG,GAAG;AAAA,MACjE,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,aAAS,KAAK,KAAK;AAAA,EACrB;AAEA,QAAM,kBAAkB,CAAC,QAAa;AACpC,QAAI,CAAC,OAAQ;AACb,UAAM,MAAM,OAAO,IAAI,QAAQ,aAAa,IAAI,IAAI,IAAI,OAAO,IAAI,OAAO,EAAE;AAC5E,UAAM,SAAS,OAAO,IAAI,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO,IAAI,UAAU,KAAK;AAC3F,UAAM,UAAU,OAAO,IAAI,YAAY,aAAa,IAAI,QAAQ,IAAI,IAAI;AACxE,WAAO,KAAK;AAAA,MACV;AAAA,MACA,KAAK,IAAI,MAAM,GAAG,GAAG;AAAA,MACrB,QAAQ;AAAA,MACR,YAAY,SAAS,aAAa;AAAA,MAClC,WAAW,KAAK,IAAI,IAAI;AAAA,IAC1B,CAAC;AAAA,EACH;AAEA,QAAM,UAAU;AAEhB,MAAI,oBAAoB;AACxB,MAAI,yBAAyB;AAE7B,SAAO;AAAA,IACL,QAAQ;AACN,eAAS;AACT,eAAS,SAAS;AAClB,aAAO,SAAS;AAChB,uBAAiB,KAAK,IAAI;AAE1B,UAAI,mBAAmB;AACrB,YAAI;AAAE,kBAAQ,GAAG,YAAY,UAAU;AAAA,QAAG,QAAQ;AAAE,8BAAoB;AAAA,QAAO;AAAA,MACjF;AACA,UAAI,wBAAwB;AAC1B,YAAI;AAAE,kBAAQ,GAAG,iBAAiB,eAAe;AAAA,QAAG,QAAQ;AAAE,mCAAyB;AAAA,QAAO;AAAA,MAChG;AAAA,IACF;AAAA,IACA,OAAO;AACL,eAAS;AACT,UAAI,mBAAmB;AACrB,YAAI;AAAE,kBAAQ,IAAI,YAAY,UAAU;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MACtD;AACA,UAAI,wBAAwB;AAC1B,YAAI;AAAE,kBAAQ,IAAI,iBAAiB,eAAe;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MAChE;AAAA,IACF;AAAA,IACA,aAAa,MAAM,CAAC,GAAG,QAAQ;AAAA,IAC/B,WAAW,MAAM,CAAC,GAAG,MAAM;AAAA,EAC7B;AACF;AAQA,eAAe,iBACb,MACA,MACA,WACe;AACf,QAAM,KAAK,KAAK,KAAK,UAAU,EAAE,WAAW,mBAAmB,CAAC;AAGhE,QAAM,KAAK,iBAAiB,eAAe,IAAM,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAGjE,QAAM,qBAAqB,MAAM,IAAI;AAErC,MAAI,WAAW;AAEb,UAAM,UAAU;AAAA,MACd;AAAA,IACF,EAAE,MAAM,MAAM;AAAA,IAEd,CAAC;AAAA,EACH,OAAO;AACL,UAAM,kBAAkB,IAAI;AAAA,EAC9B;AAGA,QAAM,KAAK,iBAAiB,eAAe,IAAM,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACnE;AAMA,eAAe,qBACb,MACA,MACe;AAEf,QAAM,KACH;AAAA,IACC;AAAA,IACA,EAAE,SAAS,KAAO;AAAA,EACpB,EACC,MAAM,MAAM;AAAA,EAAC,CAAC;AAGjB,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,aAAW,OAAO,gBAAgB;AAChC,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,QAAK,MAAM,QAAQ,MAAM,IAAK,KAAM,MAAM,QAAQ,UAAU,GAAI;AAC9D,YAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,oBAAc;AACd;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAGA,QAAM,kBAAkB,KAAK,QAAQ,wBAAwB;AAC7D,MAAK,MAAM,gBAAgB,MAAM,IAAK,KAAM,MAAM,gBAAgB,UAAU,GAAI;AAC9E,UAAM,gBAAgB,KAAK,KAAK,QAAQ;AAAA,EAC1C,OAAO;AACL,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACF;AAKA,eAAe,kBAAkB,MAA2B;AAC1D,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,EACF;AAEA,MAAI,YAAY;AAChB,aAAW,OAAO,iBAAiB;AACjC,UAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,QAAK,MAAM,QAAQ,MAAM,IAAK,KAAM,MAAM,QAAQ,UAAU,GAAI;AAC9D,YAAM,QAAQ,MAAM;AACpB,kBAAY;AACZ;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAW;AAEd,UAAM,KAAK,QAAQ,wBAAwB,EAAE,KAAK,IAAI;AAAA,EACxD;AACF;;;AEjWA,eAAsB,mBACpB,WACA,WACA,OAUA,OACiB;AACjB,QAAM,YAAY,MACf;AAAA,IACC,CAAC,MACC,QAAQ,EAAE,UAAU,KAAK,EAAE,MAAM;AAAA,cAAiB,EAAE,cAAc;AAAA,YAAe,EAAE,YAAY;AAAA,YAAe,EAAE,UAAU,YAAY,EAAE,SAAS,SAAS,MAAM,iBAAiB,KAAK,MAAM,EAAE,aAAa,GAAG,CAAC,KAAK,EAAE,QAAQ;AAAA,WAAc,EAAE,KAAK,KAAK,EAAE;AAAA,EAC9P,EACC,KAAK,MAAM;AAEd,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE;AAC9D,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE;AAC/D,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAEjD,QAAM,WAAW,YAAY,IACzB,8HACA;AAEJ,QAAM,WAAW,MAAM,UAAU,SAAS,OAAO;AAAA,IAC/C;AAAA,IACA,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS,2HAA2H,QAAQ;AAAA;AAAA,QAE5I,SAAS;AAAA,WACN,SAAS,YAAY,SAAS,YAAY,SAAS,mBAAmB,MAAM,MAAM;AAAA;AAAA,EAE3F,SAAS;AAAA,MACL;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,SAAS,QACb,OAAO,CAAC,UAAwC,MAAM,SAAS,MAAM,EACrE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK,EAAE;AACZ;;;ACnDA,IAAMA,iBAAgB;AA0BtB,eAAsB,aAAa,OAAqD;AACtF,QAAM,QAAQ,MAAM,SAASA;AAE7B,QAAM,aAAa,MAAM,iBACrB;AAAA,mBAAsB,MAAM,cAAc,KAC1C;AAEJ,QAAM,WAAW,MAAM,MAAM,UAAU,SAAS,OAAO;AAAA,IACrD;AAAA,IACA,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,YAAY;AAAA,cACZ,MAAM,MAAM,iBAAiB,SAAS,QAAQ;AAAA,YAChD;AAAA,UACF;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,YAAY;AAAA,cACZ,MAAM,MAAM,gBAAgB,SAAS,QAAQ;AAAA,YAC/C;AAAA,UACF;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA;AAAA,oBAEE,MAAM,MAAM;AAAA,mBACb,MAAM,cAAc,GAAG,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAgB1C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,OAAO,SAAS,QACnB,OAAO,CAAC,UAAwC,MAAM,SAAS,MAAM,EACrE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK,EAAE;AAEV,SAAO,gBAAgB,IAAI;AAC7B;AAMA,SAAS,gBAAgB,MAA8B;AAErD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,KAAK,CAAC;AACrC,WAAO,mBAAmB,MAAM;AAAA,EAClC,QAAQ;AAEN,UAAM,YAAY,KAAK,MAAM,oEAAoE;AACjG,QAAI,WAAW;AACb,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AACtC,eAAO,mBAAmB,MAAM;AAAA,MAClC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc,oDAAoD,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,EACtF;AACF;AAKA,SAAS,mBAAmB,QAAiD;AAC3E,SAAO;AAAA,IACL,QAAQ,OAAO,OAAO,WAAW,YAAY,OAAO,SAAS;AAAA,IAC7D,YAAY,OAAO,OAAO,eAAe,WACrC,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC,IAC1C;AAAA,IACJ,cAAc,OAAO,OAAO,iBAAiB,WACzC,OAAO,eACP;AAAA,EACN;AACF;;;AC/HA,eAAsB,cACpB,MACA,WACA,MACuB;AAEvB,MAAI,KAAK,YAAY,KAAK,YAAY;AACpC,QAAI;AACF,YAAM,wBAAwB,MAAM,IAAI;AACxC,aAAO,EAAE,eAAe,KAAK;AAAA,IAC/B,SAAS,KAAK;AAEZ,YAAM,iBAAiB,MAAM,uBAAuB,WAAW,IAAI;AACnE,aAAO;AAAA,QACL,eAAe;AAAA,QACf,OAAO,eAAe,QAClB,sBAAsB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,sCAAsC,eAAe,KAAK,KAChI;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAGA,SAAO,uBAAuB,WAAW,IAAI;AAC/C;AAMA,eAAe,wBAAwB,MAAY,MAA+B;AAChF,QAAM,EAAE,YAAY,UAAU,OAAO,OAAO,IAAI;AAIhD,UAAQ,YAAY;AAAA,IAClB,KAAK,SAAS;AACZ,YAAM,UAAU,KAAK,QAAQ,QAAS;AACtC,YAAM,QAAQ,MAAM;AACpB;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,UAAU,KAAK,QAAQ,QAAS;AACtC,YAAM,QAAQ,KAAK,SAAS,EAAE;AAC9B;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AAEb,YAAM,KAAK;AAAA,QACT,CAAC,EAAE,KAAK,IAAI,MAAoC;AAC9C,gBAAM,KAAK,SAAS,cAAc,GAAG;AACrC,cAAI,CAAC,GAAI,OAAM,IAAI,MAAM,6BAA6B,GAAG,EAAE;AAC3D,aAAG,QAAQ;AACX,aAAG,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,QACzD;AAAA,QACA,EAAE,KAAK,UAAW,KAAK,SAAS,GAAG;AAAA,MACrC;AACA;AAAA,IACF;AAAA,IACA,KAAK,YAAY;AACf,YAAM,MAAM,SAAS,YAAY;AACjC,UAAI,CAAC,IAAK,OAAM,IAAI,MAAM,2DAA2D;AACrF,YAAM,KAAK,KAAK,KAAK,EAAE,WAAW,oBAAoB,WAAW,KAAO,CAAC;AACzE;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AAEb,YAAM,KAAK,SAAS,CAAC,QAAgB;AACnC,cAAM,KAAK,SAAS,cAAc,GAAG;AACrC,YAAI,GAAI,IAAG,eAAe,EAAE,UAAU,UAAU,OAAO,SAAS,CAAC;AAAA,MACnE,GAAG,QAAS;AACZ;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,UAAU;AACZ,cAAM,KAAK,gBAAgB,UAAU,EAAE,SAAS,UAAU,IAAO,CAAC;AAAA,MACpE,WAAW,QAAQ;AACjB,cAAM,KAAK,eAAe,MAAM;AAAA,MAClC;AACA;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AAGb;AAAA,IACF;AAAA,IACA,SAAS;AACP,YAAM,IAAI,MAAM,uBAAuB,UAAU,EAAE;AAAA,IACrD;AAAA,EACF;AAGA,MAAI,UAAU,eAAe,QAAQ;AACnC,UAAM,KAAK,eAAe,MAAM;AAAA,EAClC;AACF;AAMA,eAAe,uBACb,WACA,MACuB;AACvB,MAAI;AACF,UAAM,UAAU,IAAI,KAAK,MAAM;AAC/B,WAAO,EAAE,eAAe,MAAM;AAAA,EAChC,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,eAAe;AAAA,MACf,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD;AAAA,EACF;AACF;;;ACzGA,eAAsB,iBAAiB,MAAgD;AACrF,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,SAAS,MAAM;AAGvC,YAAM,KAAM,SAAiB,mBAAmB,SAAS;AACzD,UAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,OAAO,SAAS,gBAAiB,QAAO;AAE3E,YAAM,UAAU,GAAG,SAAS,YAAY,KAAK;AAC7C,YAAM,eAAe,GAAG,eAAe,IAAI,KAAK,EAAE,MAAM,GAAG,GAAG;AAG9D,UAAI,WAAW;AACf,UAAI,WAAW;AAGf,YAAM,SAAS,GAAG,aAAa,aAAa,KAAK,GAAG,aAAa,cAAc;AAC/E,UAAI,QAAQ;AACV,mBAAW,iBAAiB,MAAM;AAClC,mBAAW;AAAA,MACb,WAES,GAAG,MAAM,CAAC,kBAAkB,KAAK,GAAG,EAAE,KAAK,CAAC,UAAU,KAAK,GAAG,EAAE,GAAG;AAC1E,mBAAW,IAAI,GAAG,EAAE;AACpB,mBAAW;AAAA,MACb,WAES,GAAG,aAAa,MAAM,GAAG;AAChC,cAAM,OAAO,GAAG,aAAa,MAAM;AACnC,cAAM,OAAO,GAAG,aAAa,YAAY,KAAK,GAAG,aAAa,MAAM,KAAK;AACzE,YAAI,MAAM;AACR,qBAAW,UAAU,IAAI,kBAAkB,IAAI;AAC/C,qBAAW;AAAA,QACb,OAAO;AACL,qBAAW,UAAU,IAAI;AACzB,qBAAW;AAAA,QACb;AAAA,MACF,WAES,GAAG,aAAa,YAAY,GAAG;AACtC,mBAAW,gBAAgB,GAAG,aAAa,YAAY,CAAC;AACxD,mBAAW;AAAA,MACb,OAEK;AACH,cAAM,QAAkB,CAAC;AACzB,YAAI,UAAU;AACd,eAAO,WAAW,YAAY,SAAS,MAAM;AAC3C,cAAI,OAAO,QAAQ,QAAQ,YAAY;AACvC,cAAI,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAE9D,kBAAM,UAAU,QAAQ,UAAU,MAAM,KAAK,EAAE;AAAA,cAC7C,CAAC,MAAc,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS;AAAA,YACvD;AACA,gBAAI,QAAQ,SAAS,GAAG;AACtB,sBAAQ,IAAI,QAAQ,CAAC,CAAC;AAAA,YACxB;AAAA,UACF;AACA,gBAAM,QAAQ,IAAI;AAClB,oBAAU,QAAQ;AAClB,cAAI,MAAM,UAAU,EAAG;AAAA,QACzB;AACA,mBAAW,MAAM,KAAK,KAAK;AAC3B,mBAAW;AAAA,MACb;AAGA,UAAI;AACJ,UAAI,YAAY,YAAY,YAAY,OAAO,GAAG,aAAa,MAAM,MAAM,UAAU;AACnF,8BAAsB;AAAA,MACxB,WAAW,YAAY,WAAW,YAAY,YAAY;AACxD,cAAM,OAAO,GAAG,aAAa,MAAM,KAAK;AACxC,YAAI,SAAS,cAAc,SAAS,SAAS;AAC3C,gCAAsB;AAAA,QACxB,OAAO;AACL,gCAAsB;AAAA,QACxB;AAAA,MACF,WAAW,YAAY,UAAU;AAC/B,8BAAsB;AAAA,MACxB;AAEA,aAAO,EAAE,UAAU,UAAU,qBAAqB,SAAS,YAAY;AAAA,IACzE,CAAC;AAED,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,oBAAoB,MAA2B;AACnE,MAAI;AACF,UAAM,KAAK,SAAS,MAAM;AACxB,eAAS,iBAAiB,SAAS,CAAC,MAAM;AACxC,QAAC,SAAiB,kBAAkB,EAAE;AAAA,MACxC,GAAG,EAAE,SAAS,KAAK,CAAC;AAAA,IACtB,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;;;ACvIA,IAAM,gBAAmE;AAAA,EACvE,4BAA4B,EAAE,OAAO,GAAK,QAAQ,GAAK;AAAA,EACvD,2BAA2B,EAAE,OAAO,KAAM,QAAQ,EAAI;AAAA,EACtD,0BAA0B,EAAE,OAAO,IAAM,QAAQ,GAAK;AAAA;AAAA,EAEtD,UAAU,EAAE,OAAO,GAAK,QAAQ,GAAK;AAAA,EACrC,SAAS,EAAE,OAAO,KAAM,QAAQ,EAAI;AAAA,EACpC,QAAQ,EAAE,OAAO,IAAM,QAAQ,GAAK;AACtC;AAEA,IAAMC,iBAAgB;AAGtB,IAAM,gBAAgB;AAAA;AAAA,EAEpB,UAAU;AAAA,EACV,WAAW;AAAA;AAAA,EAEX,cAAc;AAAA,EACd,eAAe;AAAA;AAAA,EAEf,cAAc;AAAA,EACd,eAAe;AACjB;AAmBO,SAAS,aACd,aACA,cACA,OACc;AACd,QAAM,gBAAgB,SAASA;AAC/B,QAAM,UAAU,cAAc,aAAa,KAAK,cAAcA,cAAa;AAE3E,QAAM,YAAa,cAAc,MAAa,QAAQ;AACtD,QAAM,aAAc,eAAe,MAAa,QAAQ;AACxD,QAAM,eAAe,YAAY;AACjC,QAAM,QAAQ,KAAK,MAAM,eAAe,MAAM,GAAG,IAAI;AAErD,SAAO;AAAA,IACL;AAAA,IACA,WAAW,IAAI,aAAa,QAAQ,CAAC,CAAC;AAAA,IACtC,QAAQ,EAAE,aAAa,aAAa;AAAA,IACpC,OAAO;AAAA,EACT;AACF;AAMO,SAAS,iBAAiB,WAAmB,OAA8B;AAChF,QAAM,cACJ,aAAa,cAAc,WAAW,cAAc,gBACpD,cAAc;AAChB,QAAM,eACJ,aAAa,cAAc,YAAY,cAAc,iBACrD,cAAc;AAEhB,SAAO,aAAa,aAAa,cAAc,KAAK;AACtD;AAKO,SAAS,kBACd,WACA,OACc;AACd,MAAI,aAAa;AACjB,MAAI,cAAc;AAElB,aAAW,MAAM,WAAW;AAC1B,kBACE,GAAG,aAAa,cAAc,WAAW,cAAc,gBACvD,cAAc;AAChB,mBACE,GAAG,aAAa,cAAc,YAAY,cAAc,iBACxD,cAAc;AAAA,EAClB;AAEA,SAAO,aAAa,YAAY,aAAa,KAAK;AACpD;AAMO,SAAS,iBAAiB,WAG/B;AACA,SAAO;AAAA,IACL,aACE,aAAa,cAAc,WAAW,cAAc,gBACpD,cAAc;AAAA,IAChB,cACE,aAAa,cAAc,YAAY,cAAc,iBACrD,cAAc;AAAA,EAClB;AACF;;;AP7FA,IAAM,0BAA0B;AAGhC,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAM/B,SAAS,iBAAiB,OAAwB;AAChD,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,SAAS,KAAK,OAAK,EAAE,KAAK,KAAK,CAAC;AACzC;AAGA,eAAe,YAAe,SAAqB,WAAmB,WAA+B;AACnG,MAAI;AACJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,gBAAY,WAAW,MAAM,OAAO,IAAI,MAAM,GAAG,SAAS,oBAAoB,SAAS,IAAI,CAAC,GAAG,SAAS;AAAA,EAC1G,CAAC;AACD,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAAA,EACrD,UAAE;AACA,iBAAa,SAAU;AAAA,EACzB;AACF;AAKA,eAAsB,QAAQ,QAA+C;AAC3E,QAAM,YAAY,IAAI,UAAU,EAAE,QAAQ,OAAO,gBAAgB,CAAC;AAClE,QAAM,YAAY,KAAK,IAAI;AAE3B,QAAM,gBAA+B,OAAO,WAAW;AAAA,IACrD,UAAU;AAAA,IACV,UAAU;AAAA,EACZ;AAEA,SAAO,iBAAiB,cAAc;AAEtC,MAAI;AAEJ,QAAM,cAA4B,CAAC;AAGnC,MAAI,qBAAqC,CAAC;AAC1C,MAAI,uBAAuC,CAAC;AAC5C,MAAI,gBAAgB,KAAK,IAAI;AAE7B,MAAI;AACF,cAAU,MAAM,uBAAuB,eAAe,OAAO,eAAe;AAC5E,UAAM,EAAE,WAAW,KAAK,IAAI;AAI5B,UAAM,sBAAsB,SAAS;AAMrC,UAAM,UAAU;AAGhB,QAAI;AACF,cAAQ,GAAG,WAAW,CAAC,QAAa;AAClC,cAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,QAAQ;AAC1C,cAAM,cACJ,UAAU,UAAU,UACpB,UAAU,UAAU,UAAU,YAAY,YAC1C,UAAU,SAAS,SACnB,UAAU,UAAU,UAAU;AAEhC,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,OAAO,OAAO,IAAI,SAAS,aAAa,IAAI,KAAK,IAAI,OAAO,IAAI,QAAQ,GAAG,GAAG,MAAM,GAAG,GAAI;AAAA,UAC3F,QAAQ,OAAO,IAAI,aAAa,aAAa,IAAI,SAAS,GAAG,MAAM;AAAA,UACnE,WAAW,KAAK,IAAI,IAAI;AAAA,QAC1B,CAAC;AAAA,MACH,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,cAAQ,GAAG,iBAAiB,CAAC,QAAa;AACxC,cAAM,MAAM,OAAO,IAAI,QAAQ,aAAa,IAAI,IAAI,IAAI,OAAO,IAAI,OAAO,EAAE;AAC5E,cAAM,SAAS,OAAO,IAAI,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO,IAAI,UAAU,KAAK;AAC3F,cAAM,UAAU,OAAO,IAAI,YAAY,aAAa,IAAI,QAAQ,IAAI,IAAI;AACxE,6BAAqB,KAAK;AAAA,UACxB;AAAA,UACA,KAAK,IAAI,MAAM,GAAG,GAAG;AAAA,UACrB,QAAQ;AAAA,UACR,YAAY,SAAS,aAAa;AAAA,UAClC,WAAW,KAAK,IAAI,IAAI;AAAA,QAC1B,CAAC;AAAA,MACH,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,cAAQ,GAAG,YAAY,CAAC,QAAa;AACnC,cAAM,SAAS,OAAO,IAAI,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO,IAAI,UAAU,CAAC;AACvF,YAAI,UAAU,KAAK;AACjB,gBAAM,MAAM,OAAO,IAAI,QAAQ,aAAa,IAAI,IAAI,IAAI,OAAO,IAAI,OAAO,EAAE;AAC5E,gBAAM,aAAa,OAAO,IAAI,eAAe,aAAa,IAAI,WAAW,IAAI,OAAO,IAAI,cAAc,EAAE;AACxG,gBAAM,MAAM,OAAO,IAAI,YAAY,aAAa,IAAI,QAAQ,IAAI,IAAI;AACpE,gBAAM,SAAS,MAAO,OAAO,IAAI,WAAW,aAAa,IAAI,OAAO,IAAI,OAAO,IAAI,UAAU,KAAK,IAAK;AACvG,+BAAqB,KAAK;AAAA,YACxB;AAAA,YACA,KAAK,IAAI,MAAM,GAAG,GAAG;AAAA,YACrB;AAAA,YACA;AAAA,YACA,WAAW,KAAK,IAAI,IAAI;AAAA,UAC1B,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAEA,QAAI,OAAO,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,mBAAmB;AACjF,aAAO,iBAAiB,gBAAgB;AACxC,YAAM,WAAW,MAAM,OAAO,MAAM,SAAS;AAAA,IAC/C;AAGA,WAAO,iBAAiB,YAAY;AACpC,UAAM,YAAY,OAAO,SAAS,cAC9B,GAAG,OAAO,UAAU,QAAQ,OAAO,EAAE,CAAC,GAAG,OAAO,SAAS,WAAW,KACpE,OAAO;AAEX,UAAM,KAAK,KAAK,WAAW,EAAE,WAAW,oBAAoB,WAAW,IAAO,CAAC;AAG/E,QAAI,OAAO,QAAQ,OAAO,KAAK,SAAS,gBAAgB,OAAO,KAAK,SAAS,mBAAmB;AAC9F,aAAO,iBAAiB,gBAAgB;AACxC,YAAM,WAAW,MAAM,OAAO,MAAM,SAAS;AAG7C,UAAI,OAAO,KAAK,SAAS,gBAAgB;AACvC,cAAM,KAAK,SAAS,CAAC,UAAkC;AACrD,qBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,yBAAa,QAAQ,KAAK,KAAK;AAAA,UACjC;AAAA,QACF,GAAG,OAAO,KAAK,KAAK;AACpB,cAAM,KAAK,OAAO,EAAE,WAAW,mBAAmB,CAAC;AAAA,MACrD;AAAA,IACF;AAGA,UAAM,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAIzD,UAAM,KAAK,SAAS,MAAM;AACxB,MAAC,OAAe,qBAAqB;AACrC,UAAI;AAAE,qBAAa,QAAQ,sBAAsB,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAC;AAAA,IACrE,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAGjB,UAAM,oBAAoB,IAAI;AAG9B,yBAAqB,CAAC;AACtB,2BAAuB,CAAC;AAGxB,WAAO,iBAAiB,WAAW;AACnC,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,aAAa,OAAO,OAAO,cAAc;AAC/C,UAAM,eAAe,OAAO,OAAO,gBAAgB;AACnD,UAAM,gBAAgB,OAAO,iBAAiB;AAE9C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,eAA+B,CAAC;AACtC,UAAI;AAEJ,eAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,wBAAgB,KAAK,IAAI;AAGzB,6BAAqB,CAAC;AACtB,+BAAuB,CAAC;AAGxB,cAAM,mBAAmB,MAAM,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAE9D,YAAI;AACJ,YAAI,kBAA0B;AAC9B,YAAI,eAAe;AAGnB,cAAM,eAAe,MAAM,cAAc,MAAM,WAAW,IAAI;AAC9D,gBAAQ,aAAa;AACrB,uBAAe,CAAC;AAEhB,YAAI,cAAc;AAEhB,gBAAM,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AACzD,gBAAM,KAAK,eAAe,KAAK,UAAU,GAAG;AAAA,QAC9C;AAGA,0BAAkB,MAAM,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC,EAAE,MAAM,MAAM,gBAAgB;AAGrF,YAAI,aAAa;AAAA,UACf,QAAQ;AAAA,UACR,YAAY;AAAA,UACZ,cAAc,SAAS;AAAA,QACzB;AAEA,YAAI,cAAc;AAChB,cAAI;AACF,kBAAM,eAAe,MAAM;AAAA,cACzB,aAAa;AAAA,gBACX;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,QAAQ,KAAK;AAAA,gBACb,gBAAgB,KAAK;AAAA,gBACrB,gBAAgB,KAAK;AAAA,gBACrB,OAAO,OAAO;AAAA,cAChB,CAAC;AAAA,cACD;AAAA,cACA;AAAA,YACF;AAEA,yBAAa;AAAA,cACX,QAAQ,aAAa;AAAA,cACrB,YAAY,aAAa;AAAA,cACzB,cAAc,aAAa;AAAA,YAC7B;AAAA,UACF,SAAS,SAAS;AAChB,yBAAa;AAAA,cACX,QAAQ;AAAA,cACR,YAAY;AAAA,cACZ,cAAc,4BAA4B,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,OAAO,CAAC;AAAA,YACxG;AAAA,UACF;AAAA,QACF;AAGA,YAAI,oBAAgF,CAAC;AACrF,YAAI,gBAAgB,CAAC,aAAa,eAAe;AAC/C,gBAAM,aAAa,MAAM,iBAAiB,IAAI;AAC9C,cAAI,YAAY;AACd,gCAAoB,CAAC;AAAA,cACnB,MAAM,WAAW,uBAAuB;AAAA,cACxC,UAAU,WAAW;AAAA,cACrB,aAAa,kBAAkB,WAAW,QAAQ,KAAK,WAAW,OAAO,GAAG,WAAW,cAAc,KAAK,WAAW,YAAY,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE;AAAA,YACvJ,CAAC;AAAA,UACH;AAAA,QACF;AAGA,cAAM,cAAc,mBAAmB,MAAM,GAAG,EAAE;AAClD,cAAM,gBAAgB,qBAAqB,MAAM,GAAG,EAAE;AAEtD,sBAAc;AAAA,UACZ,YAAY,KAAK;AAAA,UACjB,QAAQ,KAAK;AAAA,UACb,gBAAgB,KAAK;AAAA,UACrB,cAAc,WAAW;AAAA,UACzB,QAAQ,WAAW;AAAA,UACnB,YAAY,WAAW;AAAA,UACvB;AAAA,UACA;AAAA,UACA,cAAc;AAAA,UACd;AAAA,UACA,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,UACA,SAAS;AAAA,QACX;AAGA,cAAM,cAAc,CAAC,WAAW,UAC3B,SACA,iBAAiB,KAAK,KACtB,UAAU;AAEf,YAAI,CAAC,YAAa;AAGlB,qBAAa,KAAK;AAAA,UAChB;AAAA,UACA;AAAA,UACA,YAAY,WAAW;AAAA,UACvB,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAGD,cAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,YAAY,CAAC;AAAA,MAChE;AAGA,UAAI,iBAAiB,eAAe,CAAC,YAAY,QAAQ;AACvD,oBAAY,UAAU;AACtB,oBAAY,aAAa;AAEzB,YAAI;AACF,iBAAO,iBAAiB,YAAY;AACpC,gBAAM,KAAK,KAAK,WAAW,EAAE,WAAW,oBAAoB,WAAW,IAAO,CAAC;AAC/E,gBAAM,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AACzD,gBAAM,oBAAoB,IAAI;AAE9B,gBAAM,KAAK,SAAS,MAAM;AACxB,YAAC,OAAe,qBAAqB;AACrC,gBAAI;AAAE,2BAAa,QAAQ,sBAAsB,MAAM;AAAA,YAAG,QAAQ;AAAA,YAAC;AAAA,UACrE,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AACjB,+BAAqB,CAAC;AACtB,iCAAuB,CAAC;AACxB,iBAAO,iBAAiB,WAAW;AAAA,QACrC,SAAS,aAAa;AACpB,sBAAY,aAAa,sCAAsC,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW,CAAC;AAAA,QACzI;AAAA,MACF;AAEA,kBAAY,KAAK,WAAY;AAC7B,aAAO,iBAAiB,aAAc,GAAG,MAAM,MAAM;AAAA,IACvD;AAGA,WAAO,iBAAiB,WAAW;AACnC,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,UAAU,MAAM,mBAAmB,WAAW,OAAO,SAAS,OAAO,aAAa,KAAK;AAG7F,UAAM,gBAAgB,uBAAuB,WAAW;AAExD,WAAO;AAAA,MACL,YAAY,OAAO,SAAS;AAAA,MAC5B,eAAe,OAAO,SAAS;AAAA,MAC/B;AAAA,MACA,OAAO;AAAA,MACP,iBAAiB,KAAK,IAAI,IAAI;AAAA,MAC9B;AAAA,MACA,gBAAgB,CAAC;AAAA,MACjB,YAAY,iBAAiB,MAAM,MAAM;AAAA,MACzC,kBAAkB,QAAS;AAAA,IAC7B;AAAA,EACF,SAAS,KAAK;AAEZ,WAAO;AAAA,MACL,YAAY,OAAO,SAAS;AAAA,MAC5B,eAAe,OAAO,SAAS;AAAA,MAC/B,eAAe;AAAA,MACf,OAAO;AAAA,MACP,iBAAiB,KAAK,IAAI,IAAI;AAAA,MAC9B,SAAS,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACnF,gBAAgB,CAAC;AAAA,MACjB,YAAY,iBAAiB,YAAY,MAAM;AAAA,MAC/C,kBAAkB,SAAS,aAAa;AAAA,IAC1C;AAAA,EACF,UAAE;AAEA,QAAI,SAAS,MAAM;AACjB,YAAM,UAAU,QAAQ;AACxB,cAAQ,qBAAqB,SAAS;AACtC,cAAQ,qBAAqB,eAAe;AAC5C,cAAQ,qBAAqB,UAAU;AAAA,IACzC;AACA,UAAM,SAAS,MAAM;AAAA,EACvB;AACF;AAKA,SAAS,uBAAuB,OAAoC;AAClE,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AACjD,QAAM,eAAe,MAAM,SAAS,WAAW;AAG/C,MAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,QAAM,sBAAsB,WAAW,MAAM,CAAC,MAAM,EAAE,MAAM;AAC5D,QAAM,YAAY,WAAW,KAAK,CAAC,MAAM,EAAE,KAAK;AAGhD,MAAI,eAAe,KAAK,oBAAqB,QAAO;AAGpD,MAAI,oBAAqB,QAAO;AAGhC,MAAI,WAAW,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,UAAW,QAAO;AAG5D,SAAO;AACT;;;AQtbA,OAAOC,gBAAe;AAWtB,IAAMC,iBAAgB;AAGtB,IAAMC,2BAA0B;AAGhC,eAAeC,aAAe,SAAqB,WAAmB,WAA+B;AACnG,MAAI;AACJ,QAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,gBAAY,WAAW,MAAM,OAAO,IAAI,MAAM,GAAG,SAAS,oBAAoB,SAAS,IAAI,CAAC,GAAG,SAAS;AAAA,EAC1G,CAAC;AACD,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAAA,EACrD,UAAE;AACA,iBAAa,SAAU;AAAA,EACzB;AACF;AAEA,eAAsB,eAAe,QAAuD;AAC1F,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQF;AAAA,IACR;AAAA,EACF,IAAI;AAEJ,QAAM,YAAY,IAAIG,WAAU,EAAE,QAAQ,gBAAgB,CAAC;AAC3D,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,UAA+B,CAAC;AACtC,MAAI,mBAAmB;AACvB,MAAI,oBAAoB;AAExB,QAAM,UAAU,MAAM,uBAAuB,eAAe,eAAe;AAC3E,QAAM,EAAE,WAAW,KAAK,IAAI;AAG5B,QAAM,sBAAsB,SAAS;AAErC,MAAI;AACF,UAAM,KAAK,KAAK,WAAW,EAAE,WAAW,eAAe,WAAW,IAAO,CAAC;AAE1E,QAAI,MAAM;AACR,YAAM,WAAW,MAAM,MAAM,SAAS;AACtC,YAAM,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC3D;AAEA,UAAM,iBAAiB,qBAAqB,IAAI;AAGhD,QAAI,cAA8B,CAAC;AACnC,QAAI,kBAAkB,KAAK,IAAI;AAC/B,UAAM,UAAU;AAChB,YAAQ,GAAG,WAAW,CAAC,QAAa;AAClC,YAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,QAAQ;AAC1C,UAAI,CAAC,SAAS,WAAW,MAAM,EAAE,SAAS,KAAK,GAAG;AAChD,oBAAY,KAAK;AAAA,UACf,OAAO,UAAU,SAAS,YAAY;AAAA,UACtC,OAAO,OAAO,IAAI,SAAS,aAAa,IAAI,KAAK,IAAI,OAAO,IAAI,QAAQ,GAAG,GAAG,MAAM,GAAG,GAAG;AAAA,UAC1F,QAAQ,OAAO,IAAI,aAAa,aAAa,IAAI,SAAS,GAAG,MAAM;AAAA,UACnE,WAAW,KAAK,IAAI,IAAI;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,UAAM,YAAsB,CAAC;AAE7B,aAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,wBAAkB,KAAK,IAAI;AAC3B,oBAAc,CAAC;AAGf,YAAM,eAAe,MAAMD;AAAA,QACzB,UAAU,QAAQ;AAAA,QAClBD;AAAA,QACA;AAAA,MACF;AAGA,YAAM,mBAAmB,MAAMC;AAAA,QAC7B,UAAU,SAAS,OAAO;AAAA,UACxB,OAAO,MAAM,QAAQ,cAAc,EAAE;AAAA,UACrC,YAAY;AAAA,UACZ,QAAQ,oBAAoB,oBAAoB,eAAe,GAAG,SAAS;AAAA,UAC3E,UAAU;AAAA,YACR;AAAA,cACE,MAAM;AAAA,cACN,SAAS,qBAAqB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EAAsC,mBAAmB,YAAY,CAAC;AAAA;AAAA;AAAA,YAChH;AAAA,UACF;AAAA,QACF,CAAC;AAAA,QACDD;AAAA,QACA;AAAA,MACF;AAEA,YAAM,aAAa,YAAY,gBAAgB;AAC/C,0BAAoB,iBAAiB,MAAM;AAC3C,2BAAqB,iBAAiB,MAAM;AAE5C,UAAI,WAAW,YAAY,EAAE,SAAS,QAAQ,KAAK,WAAW,YAAY,EAAE,SAAS,iBAAiB,GAAG;AACvG;AAAA,MACF;AAGA,YAAM,mBAAmB,MAAM,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAC9D,qBAAe,MAAM;AAGrB,UAAI;AACF,cAAM,UAAU,IAAI,UAAU;AAAA,MAChC,SAAS,UAAU;AACjB,uBAAe,KAAK;AACpB,cAAMG,mBAAkB,MAAM,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAE7D,cAAMC,UAA4B;AAAA,UAChC,cAAc,IAAI;AAAA,UAClB,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,aAAa,kBAAkB,oBAAoB,QAAQ,SAAS,UAAU,OAAO,QAAQ,CAAC;AAAA,UAC9F;AAAA,UACA,iBAAAD;AAAA,UACA,iBAAiB,eAAe,YAAY;AAAA,UAC5C,aAAa,CAAC,GAAG,WAAW;AAAA,UAC5B,YAAY,KAAK,IAAI,IAAI;AAAA,QAC3B;AACA,gBAAQ,KAAKC,OAAM;AACnB,kBAAU,KAAK,IAAI,IAAI,CAAC,KAAK,UAAU,eAAeA,QAAO,WAAW,EAAE;AAC1E,2BAAmBA,SAAQ,CAAC;AAC5B;AAAA,MACF;AAGA,YAAM,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACzD,YAAM,KAAK,eAAe,GAAG;AAC7B,qBAAe,KAAK;AAGpB,YAAM,kBAAkB,MAAM,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAC7D,YAAM,mBAAmB,eAAe,YAAY;AACpD,YAAM,gBAAgB,eAAe,UAAU;AAG/C,YAAM,eAAe,MAAMH;AAAA,QACzB,UAAU,SAAS,OAAO;AAAA,UACxB,OAAO,MAAM,QAAQ,cAAc,EAAE;AAAA,UACrC,YAAY;AAAA,UACZ,QAAQ,sBAAsB;AAAA,UAC9B,UAAU;AAAA,YACR;AAAA,cACE,MAAM;AAAA,cACN,SAAS,uBAAuB,YAAY,aAAa,eAAe,KAAK,IAAI,CAAC;AAAA,YACpF;AAAA,UACF;AAAA,QACF,CAAC;AAAA,QACDD;AAAA,QACA;AAAA,MACF;AAEA,0BAAoB,aAAa,MAAM;AACvC,2BAAqB,aAAa,MAAM;AAExC,YAAM,aAAaK,iBAAgB,YAAY,YAAY,CAAC;AAG5D,YAAM,SAA4B;AAAA,QAChC,cAAc,IAAI;AAAA,QAClB,QAAQ;AAAA,QACR,UAAU,WAAW;AAAA,QACrB,UAAU,WAAW;AAAA,QACrB,YAAY,WAAW;AAAA,QACvB,aAAa,WAAW;AAAA,QACxB;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,aAAa,CAAC,GAAG,WAAW;AAAA,QAC5B,YAAY,WAAW;AAAA,QACvB,YAAY,KAAK,IAAI,IAAI;AAAA,MAC3B;AAEA,cAAQ,KAAK,MAAM;AACnB,YAAM,WAAW,WAAW,aAAa,WACrC,IAAI,IAAI,CAAC,KAAK,UAAU,WACxB,IAAI,IAAI,CAAC,KAAK,UAAU,gBAAgB,WAAW,QAAQ,MAAM,WAAW,WAAW;AAC3F,gBAAU,KAAK,QAAQ;AACvB,yBAAmB,QAAQ,CAAC;AAAA,IAC9B;AAGA,UAAM,EAAE,2BAAAC,2BAA0B,IAAI,MAAM,OAAO,iCAAoB;AACvE,UAAM,SAAS,MAAMA,2BAA0B,WAAW;AAAA,MACxD,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,MAAM,QAAQ,cAAc,EAAE;AAAA,IACvC,CAAC;AAED,wBAAoB,OAAO,WAAW;AACtC,yBAAqB,OAAO,WAAW;AAEvC,UAAM,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAE9D,WAAO;AAAA,MACL,eAAe,SAAS,SAAS,IAAI,aAAa;AAAA,MAClD;AAAA,MACA,QAAQ,OAAO;AAAA,MACf,iBAAiB,KAAK,IAAI,IAAI;AAAA,MAC9B,YAAY,EAAE,aAAa,kBAAkB,cAAc,kBAAkB;AAAA,MAC7E,kBAAkB,QAAQ;AAAA,IAC5B;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,eAAe;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,QACN,aAAa;AAAA,QACb;AAAA,QACA;AAAA,QACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,QACnC,UAAU,GAAG,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI,CAAC;AAAA,QACxD,aAAa,QAAQ;AAAA,QACrB;AAAA,QACA,UAAU,CAAC;AAAA,QACX,QAAQ,CAAC;AAAA,QACT,WAAW,CAAC,EAAE,aAAa,oCAAoC,QAAQ,OAAO,KAAK,EAAE,CAAC;AAAA,QACtF,SAAS,4BAA4B,QAAQ,MAAM,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QACtH,iBAAiB;AAAA,MACnB;AAAA,MACA,iBAAiB,KAAK,IAAI,IAAI;AAAA,MAC9B,YAAY,EAAE,aAAa,kBAAkB,cAAc,kBAAkB;AAAA,MAC7E,kBAAkB,QAAQ;AAAA,IAC5B;AAAA,EACF,UAAE;AAEA,QAAI,QAAQ,MAAM;AAChB,YAAM,UAAU,QAAQ;AACxB,cAAQ,qBAAqB,SAAS;AAAA,IACxC;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;AAIA,SAAS,oBAAoB,oBAA4B,iBAAyB,WAA6B;AAC7G,SAAO,4DAA4D,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAU5E,eAAe;AAAA,EACxB,UAAU,SAAS,IAAI;AAAA;AAAA,EAA6B,UAAU,KAAK,IAAI,CAAC,KAAK,EAAE;AAAA;AAAA;AAAA;AAIjF;AAEA,SAAS,wBAAgC;AACvC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBT;AAEA,SAAS,uBACP,QACA,aACA,eACA,YACQ;AACR,MAAI,UAAU,sBAAsB,MAAM;AAAA,eAAmB,UAAU;AAAA;AAEvE,MAAI,YAAY,SAAS,GAAG;AAC1B,eAAW;AAAA;AAAA,EAAsB,YAAY,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,EAC9F;AAEA,MAAI,cAAc,SAAS,GAAG;AAC5B,eAAW;AAAA;AAAA,EAA+B,cAAc,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,EACtI;AAEA,SAAO;AACT;AAIA,SAAS,mBAAmB,cAAmE;AAC7F,SAAO,aACJ,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,KAAK,EAAE,WAAW,EAAE,EAC1D,KAAK,IAAI;AACd;AAEA,SAAS,YAAY,UAAqC;AACxD,QAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,SAAO,MAAM,SAAS,SAAS,MAAM,OAAO;AAC9C;AAWA,SAASD,iBAAgB,MAAgC;AACvD,MAAI;AACF,UAAM,YAAY,KAAK,MAAM,aAAa;AAC1C,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,eAAe;AAC/C,UAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AACtC,WAAO;AAAA,MACL,UAAU,OAAO,YAAY;AAAA,MAC7B,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;AAAA,MACxE,aAAa,OAAO,eAAe;AAAA,MACnC,kBAAkB,OAAO;AAAA,MACzB,YAAY,OAAO,cACf,EAAE,UAAU,OAAO,aAAa,aAAa,IAAI,YAAY,GAAG,IAChE;AAAA,IACN;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,UAAU,UAAU,YAAY,KAAK,aAAa,KAAK;AAAA,EAClE;AACF;;;ACpWA,IAAME,iBAAgB;AAoDtB,eAAsB,aAAa,OAA2C;AAC5E,QAAM,QAAQ,MAAM,SAASA;AAC7B,QAAM,EAAE,QAAQ,cAAc,IAAI;AAElC,QAAM,SAAS,kBAAkB,QAAQ,aAAa;AAEtD,QAAM,WAAW,MAAM,MAAM,UAAU,SAAS,OAAO;AAAA,IACrD;AAAA,IACA,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,EAC9C,CAAC;AAED,QAAM,OAAO,SAAS,QACnB,OAAO,CAAC,UAAwC,MAAM,SAAS,MAAM,EACrE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK,EAAE;AAEV,SAAO,kBAAkB,IAAI;AAC/B;AAIA,SAAS,kBACP,QACA,eACQ;AACR,QAAM,WAAqB,CAAC;AAG5B,WAAS,KAAK,iBAAiB,OAAO,SAAS,YAAY,EAAE;AAC7D,WAAS,KAAK,gBAAgB,OAAO,WAAW,EAAE;AAClD,MAAI,OAAO,eAAe;AACxB,aAAS,KAAK,WAAW,OAAO,aAAa,EAAE;AAAA,EACjD;AAGA,MAAI,OAAO,eAAe,OAAO,KAAK,OAAO,WAAW,EAAE,SAAS,GAAG;AACpE,UAAM,MAAM,OAAO;AACnB,UAAM,QAAkB,CAAC;AACzB,QAAI,IAAI,aAAc,OAAM,KAAK,UAAU,IAAI,YAAY,EAAE;AAC7D,QAAI,IAAI,WAAY,OAAM,KAAK,QAAQ,IAAI,UAAU,EAAE;AACvD,QAAI,IAAI,cAAe,OAAM,KAAK,cAAc,IAAI,aAAa,EAAE;AACnE,QAAI,IAAI,WAAY,OAAM,KAAK,gBAAgB,IAAI,UAAU,EAAE;AAC/D,QAAI,MAAM,SAAS,GAAG;AACpB,eAAS,KAAK;AAAA,EAAiB,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,IACnD;AAAA,EACF;AAGA,MAAI,OAAO,kBAAkB;AAC3B,UAAM,WAAW,OAAO;AACxB,UAAM,cAAc,SAAS;AAC7B,QAAI,eAAe,YAAY,SAAS,GAAG;AACzC,YAAM,SAAS,YACZ,OAAO,CAAC,MAAM,EAAE,UAAU,WAAW,EAAE,UAAU,SAAS,EAC1D,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,EACnC,KAAK,IAAI;AACZ,UAAI,QAAQ;AACV,iBAAS,KAAK;AAAA,EAAoB,MAAM,EAAE;AAAA,MAC5C;AAAA,IACF;AAEA,UAAM,gBAAgB,SAAS;AAC/B,QAAI,iBAAiB,cAAc,SAAS,GAAG;AAC7C,YAAM,YAAY,cACf,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,WAAM,EAAE,MAAM,EAAE,EAC/C,KAAK,IAAI;AACZ,eAAS,KAAK;AAAA,EAAoB,SAAS,EAAE;AAAA,IAC/C;AAAA,EACF;AAGA,MAAI,OAAO,eAAe,OAAO,KAAK,OAAO,WAAW,EAAE,SAAS,GAAG;AACpE,UAAM,SAAS,OAAO;AACtB,UAAM,QAAkB,CAAC;AACzB,QAAI,OAAO,SAAU,OAAM,KAAK,aAAa,OAAO,QAAQ,EAAE;AAC9D,QAAI,OAAO,QAAS,OAAM,KAAK,YAAY,OAAO,OAAO,EAAE;AAC3D,QAAI,OAAO,GAAI,OAAM,KAAK,OAAO,OAAO,EAAE,EAAE;AAC5C,QAAI,OAAO,WAAY,OAAM,KAAK,WAAW,OAAO,UAAU,EAAE;AAChE,QAAI,MAAM,SAAS,GAAG;AACpB,eAAS,KAAK;AAAA,EAAY,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,IAC9C;AAAA,EACF;AAGA,MAAI,OAAO,mBAAmB;AAC5B,aAAS,KAAK,sBAAsB,OAAO,iBAAiB,EAAE;AAAA,EAChE;AAGA,MAAI,gBAAgB;AACpB,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,cAAc,cAAc,IAAI,CAAC,MAAM;AAC3C,YAAM,OAAO,EAAE,YAAY,MAAM,GAAG,GAAG;AACvC,YAAM,KAAK,EAAE,oBAAoB,kBAAkB,EAAE,iBAAiB,MAAM;AAC5E,aAAO,SAAS,EAAE,EAAE,OAAO,EAAE,SAAS,YAAY,OAAO,IAAI,GAAG,EAAE;AAAA,IACpE,CAAC;AACD,oBAAgB;AAAA;AAAA,EAA6C,YAAY,KAAK,IAAI,CAAC;AAAA,EACrF;AAEA,SAAO;AAAA;AAAA,EAEP,SAAS,KAAK,MAAM,CAAC;AAAA,EACrB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+Bf;AAIA,IAAM,mBAAqC,CAAC,YAAY,QAAQ,UAAU,KAAK;AAC/E,IAAM,mBAAqC,CAAC,SAAS,cAAc,SAAS,YAAY,OAAO;AAE/F,SAAS,kBAAkB,MAA4B;AAErD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,KAAK,CAAC;AACrC,WAAO,qBAAqB,MAAM;AAAA,EACpC,QAAQ;AAEN,UAAM,YAAY,KAAK,MAAM,mEAAmE;AAChG,QAAI,WAAW;AACb,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AACtC,eAAO,qBAAqB,MAAM;AAAA,MACpC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,qBAAqB,yCAAyC,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IAChF,cAAc;AAAA,IACd,sBAAsB;AAAA,IACtB,cAAc;AAAA,EAChB;AACF;AAEA,SAAS,qBAAqB,QAA+C;AAC3E,QAAM,WAAW,iBAAiB,SAAS,OAAO,kBAAoC,IACjF,OAAO,qBACR;AAEJ,QAAM,WAAW,iBAAiB,SAAS,OAAO,kBAAoC,IACjF,OAAO,qBACR;AAEJ,SAAO;AAAA,IACL,oBAAoB;AAAA,IACpB,qBAAqB,gBAAgB,OAAO,mBAAmB;AAAA,IAC/D,oBAAoB;AAAA,IACpB,qBAAqB,gBAAgB,OAAO,mBAAmB;AAAA,IAC/D,qBAAqB,OAAO,OAAO,wBAAwB,WACvD,OAAO,sBACP;AAAA,IACJ,cAAc,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe;AAAA,IAC9E,sBAAsB,gBAAgB,OAAO,oBAAoB;AAAA,IACjE,cAAc,OAAO,OAAO,iBAAiB,WACzC,OAAO,eACP;AAAA,EACN;AACF;AAEA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACvC;;;AC7PA,IAAMC,iBAAgB;AAuDtB,eAAsB,eAAe,OAAuD;AAC1F,QAAM,QAAQ,MAAM,SAASA;AAC7B,QAAM,EAAE,MAAM,QAAQ,oBAAoB,aAAa,cAAc,IAAI;AAEzE,QAAM,UAAkD,CAAC;AAGzD,UAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,2DAA2D,CAAC;AAC/F,UAAQ,KAAK;AAAA,IACX,MAAM;AAAA,IACN,QAAQ,EAAE,MAAM,UAAU,YAAY,aAAa,MAAM,OAAO,iBAAiB,SAAS,QAAQ,EAAE;AAAA,EACtG,CAAC;AAGD,UAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,yDAAyD,CAAC;AAC7F,UAAQ,KAAK;AAAA,IACX,MAAM;AAAA,IACN,QAAQ,EAAE,MAAM,UAAU,YAAY,aAAa,MAAM,OAAO,gBAAgB,SAAS,QAAQ,EAAE;AAAA,EACrG,CAAC;AAGD,UAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,mBAAmB,MAAM,QAAQ,oBAAoB,aAAa,aAAa,EAAE,CAAC;AAErH,QAAM,WAAW,MAAM,MAAM,UAAU,SAAS,OAAO;AAAA,IACrD;AAAA,IACA,YAAY;AAAA,IACZ,UAAU,CAAC,EAAE,MAAM,QAAQ,QAAQ,CAAC;AAAA,EACtC,CAAC;AAED,QAAM,OAAO,SAAS,QACnB,OAAO,CAAC,UAAwC,MAAM,SAAS,MAAM,EACrE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAK,EAAE;AAEV,SAAO,qBAAqB,MAAM,IAAI;AACxC;AAIA,IAAM,cAAuE;AAAA,EAC3E,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,OAAO;AAAA,EACP,SAAS;AACX;AAYO,SAAS,4BACd,qBAC0B;AAC1B,MAAI,oBAAoB,WAAW,EAAG,QAAO;AAE7C,MAAI,oBAAoB,KAAK,CAAC,MAAM,MAAM,UAAU,EAAG,QAAO;AAC9D,MAAI,oBAAoB,MAAM,CAAC,MAAM,MAAM,eAAe,EAAG,QAAO;AACpE,MAAI,oBAAoB,MAAM,CAAC,MAAM,MAAM,kBAAkB,EAAG,QAAO;AACvE,MAAI,oBAAoB,MAAM,CAAC,MAAM,MAAM,OAAO,EAAG,QAAO;AAG5D,QAAM,SAAS,oBAAI,IAAmC;AACtD,aAAW,KAAK,qBAAqB;AACnC,WAAO,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,EACxC;AAEA,MAAI,OAA8B;AAClC,MAAI,YAAY;AAChB,aAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,QAAI,QAAQ,WAAW;AACrB,kBAAY;AACZ,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,YAAY,IAAI;AACzB;AAIA,SAAS,mBACP,MACA,QACA,oBACA,aACA,eACQ;AACR,QAAM,WAAqB,CAAC;AAE5B,WAAS,KAAK,gBAAgB,KAAK,UAAU,KAAK,KAAK,MAAM,EAAE;AAC/D,WAAS,KAAK,aAAa,KAAK,cAAc,EAAE;AAChD,WAAS,KAAK,WAAW,OAAO,YAAY,EAAE;AAE9C,MAAI,KAAK,SAAU,UAAS,KAAK,kBAAkB,KAAK,QAAQ,EAAE;AAClE,MAAI,KAAK,WAAY,UAAS,KAAK,gBAAgB,KAAK,UAAU,EAAE;AACpE,MAAI,OAAO,MAAO,UAAS,KAAK,UAAU,OAAO,KAAK,EAAE;AAExD,MAAI,oBAAoB;AACtB,aAAS,KAAK,0DAA0D,mBAAmB,QAAQ,SAAS,mBAAmB,QAAQ,IAAI,mBAAmB,cAAc,kBAAa,mBAAmB,WAAW,MAAM,EAAE,EAAE;AAAA,EACnO;AAEA,MAAI,eAAe,YAAY,SAAS,GAAG;AACzC,UAAM,SAAS,YACZ,OAAO,CAAC,MAAM,EAAE,UAAU,WAAW,EAAE,UAAU,SAAS,EAC1D,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,EACnC,KAAK,IAAI;AACZ,QAAI,OAAQ,UAAS,KAAK;AAAA,EAAoB,MAAM,EAAE;AAAA,EACxD;AAEA,MAAI,iBAAiB,cAAc,SAAS,GAAG;AAC7C,UAAM,YAAY,cACf,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,WAAM,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,EAC/D,KAAK,IAAI;AACZ,aAAS,KAAK;AAAA,EAAoB,SAAS,EAAE;AAAA,EAC/C;AAEA,SAAO;AAAA;AAAA,EAEP,SAAS,KAAK,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBvB;AAIA,IAAM,wBAAiD,CAAC,YAAY,oBAAoB,iBAAiB,SAAS,SAAS;AAE3H,SAAS,qBAAqB,MAAc,MAAqD;AAE/F,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,KAAK,CAAC;AACrC,WAAO,wBAAwB,QAAQ,IAAI;AAAA,EAC7C,QAAQ;AAEN,UAAM,YAAY,KAAK,MAAM,uDAAuD;AACpF,QAAI,WAAW;AACb,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,UAAU,CAAC,CAAC;AACtC,eAAO,wBAAwB,QAAQ,IAAI;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,WAAW,mDAAmD,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,EAClF;AACF;AAEA,SAAS,wBACP,QACA,MACiB;AACjB,QAAM,iBAAiB,sBAAsB,SAAS,OAAO,cAAuC,IAC/F,OAAO,iBACR;AAEJ,QAAM,SAA0B;AAAA,IAC9B;AAAA,IACA,YAAYC,iBAAgB,OAAO,UAAU;AAAA,IAC7C,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AAAA,EACvE;AAGA,MAAI,OAAO,iBAAiB,OAAO,OAAO,kBAAkB,UAAU;AACpE,UAAM,MAAM,OAAO;AACnB,WAAO,gBAAgB;AAAA,MACrB,YAAY,KAAK;AAAA,MACjB,iBAAiB,KAAK;AAAA,MACtB,kBAAkB,OAAO,IAAI,qBAAqB,WAAW,IAAI,mBAAmB;AAAA,MACpF,oBAAoB,OAAO,IAAI,uBAAuB,WAAW,IAAI,qBAAqB;AAAA,MAC1F,sBAAsB,OAAO,IAAI,yBAAyB,WAAW,IAAI,uBAAuB;AAAA,MAChG,iBAAiB,OAAO,IAAI,oBAAoB,WAAW,IAAI,kBAAkB;AAAA,IACnF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAASA,iBAAgB,OAAwB;AAC/C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACvC;;;ACpRO,IAAM,YAAN,MAAgB;AAAA,EAIrB,YAA6B,KAAa;AAAb;AAH7B,SAAQ,UAAU;AAClB,SAAQ,QAA2B,CAAC;AAGlC,QAAI,MAAM,EAAG,OAAM,IAAI,MAAM,4BAA4B;AAAA,EAC3D;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAU,KAAK,KAAK;AAC3B,WAAK;AACL;AAAA,IACF;AACA,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,WAAK,MAAM,KAAK,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,UAAgB;AACd,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,MAAM;AAER,WAAK;AAAA,IACP,OAAO;AACL,WAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,UAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":["DEFAULT_MODEL","DEFAULT_MODEL","Anthropic","DEFAULT_MODEL","AI_OPERATION_TIMEOUT_MS","withTimeout","Anthropic","screenshotAfter","action","parseEvaluation","generateExplorationReport","DEFAULT_MODEL","DEFAULT_MODEL","clampConfidence"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbearai/ai-executor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "AI-powered QA test executor using Playwright and Claude Vision",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -56,5 +56,9 @@
|
|
|
56
56
|
"tsup": "^8.0.0",
|
|
57
57
|
"typescript": "^5.3.0",
|
|
58
58
|
"vitest": "^3.0.0"
|
|
59
|
+
},
|
|
60
|
+
"overrides": {
|
|
61
|
+
"@langchain/core": "^1.1.20",
|
|
62
|
+
"langsmith": "^0.5.0"
|
|
59
63
|
}
|
|
60
64
|
}
|