{"version":3,"file":"cli.mjs","names":["chromeForTestingConfig.version","chromeForTestingConfig.dockerImage","chromeForTestingConfig.dockerPlatform","styles.tableContainer","styles.emptyState","styles.table","styles.browserRow","styles.statusActive","styles.timestampCell","styles.actionsCell","styles.killButton","styles.pageRow","styles.urlCell","styles.statsContainer","styles.statCard","styles.container","styles.header","styles.refreshButton","styles.buttonDanger","styles.statCard","styles.tableContainer","styles.emptyState","styles.browserRow","styles.timestampCell","styles.pageRow","styles.urlCell","styles.statusActive","styles.actionsCell","styles.killButton","parsePort","fs","packageJson.version"],"sources":["../package.json","../src/server/extension-routes.ts","../src/commands/chrome-serve.ts","../src/config.ts","../src/utils/httpClient.ts","../src/utils/outputFormatter.ts","../src/commands/custom-tools.ts","../src/commands/docker-build-cft.ts","../src/commands/exec.ts","../src/services/CustomToolService.ts","../src/server/dashboard/routes/api.ts","../src/server/dashboard/utils/formatters.ts","../src/server/dashboard/components/styles.ts","../src/server/dashboard/components/BrowserTable.tsx","../src/server/dashboard/components/StatsHeader.tsx","../src/server/dashboard/components/Dashboard.tsx","../src/server/dashboard/components/Layout.tsx","../src/server/dashboard/routes/dashboard.tsx","../src/server/middleware/originGuard.ts","../src/server/http.ts","../src/server/websocket-routes.ts","../src/utils/tempDirCleanup.ts","../src/commands/http-serve.ts","../src/constants/tool-tags.ts","../src/server/proxy.ts","../src/services/McpOwnerTrackerRegistry.ts","../src/commands/mcp-serve.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/utils/cliArgs.ts","../src/utils/commandBuilder.ts","../src/utils/toolCommands.ts","../src/cli.ts"],"sourcesContent":["","/**\n * Extension HTTP Routes\n *\n * DESIGN PATTERNS:\n * - Hono router for HTTP routing\n * - RESTful API endpoints for extension communication\n * - Dependency injection with Container\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing\n * - Keep route handlers thin, delegate to services\n * - Return appropriate HTTP status codes\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type {\n  HandoffRequest,\n  IExtensionSessionRegistry,\n  SessionHeartbeatRequest,\n  SessionRegistrationRequest,\n} from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue, ExtensionTaskResult } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport {\n  type ITelemetryService,\n  resolveBrowserTelemetryConfig,\n  TelemetryService,\n} from '../services/TelemetryService.js';\n\n/**\n * Task request body from extension polling\n */\ninterface TaskPollResponse {\n  task?: {\n    id: string;\n    tool: string;\n    arguments: Record<string, unknown>;\n    telemetry?: {\n      traceId?: string;\n      parentSpanId?: string;\n    };\n  };\n}\n\n/**\n * Result submission body from extension\n */\ninterface ResultSubmitRequest {\n  taskId: string;\n  success: boolean;\n  result?: {\n    content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n    isError?: boolean;\n  };\n  error?: string;\n}\n\ninterface TabMappedRequest {\n  pageId: string;\n  tabId: number;\n}\n\ninterface RecordingArtifactRequest {\n  browserId: string;\n  videoBase64?: string;\n}\n\ninterface RecordingChunkRequest {\n  browserId: string;\n  chunkBase64: string;\n  mimeType?: string;\n  chunkIndex?: number;\n}\n\ninterface BrowserLogRequest {\n  level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n  message: string;\n  attributes?: Record<string, unknown>;\n}\n\nconst DEBUG_EXTENSION_RECORDING_STDOUT = process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1';\n\nfunction emitExtensionRecordingDebug(message: string, details?: Record<string, unknown>): void {\n  if (!DEBUG_EXTENSION_RECORDING_STDOUT) {\n    return;\n  }\n\n  const payload = details ? ` ${JSON.stringify(details)}` : '';\n  console.log(`[ExtensionRecordingDebug] ${message}${payload}`);\n}\n\n/**\n * Status response\n */\ninterface StatusResponse {\n  connected: boolean;\n  lastPollAt?: string;\n  lastResultAt?: string;\n  pendingTasks: number;\n  queueSize: number;\n}\n\n/**\n * Create extension routes for Chrome extension communication\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono router\n */\n/**\n * Upload ceilings for recording payloads.\n *\n * These bodies are base64 and are parsed into memory before anything validates\n * them, so without a ceiling a single request can exhaust the daemon's heap.\n * A chunk covers one MediaRecorder timeslice; the session upload covers a whole\n * recording.\n */\nconst RECORDING_CHUNK_BODY_LIMIT = bodyLimit({\n  maxSize: 24 * 1024 * 1024,\n  onError: (c) => c.json({ error: 'Recording chunk exceeds the maximum accepted size' }, 413),\n});\n\nconst RECORDING_BODY_LIMIT = bodyLimit({\n  maxSize: 128 * 1024 * 1024,\n  onError: (c) => c.json({ error: 'Recording exceeds the maximum accepted size' }, 413),\n});\n\nexport function createExtensionRoutes(container: Container): Hono {\n  const router = new Hono();\n  let telemetry: ITelemetryService;\n  try {\n    telemetry = container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n  } catch {\n    telemetry = new TelemetryService();\n  }\n\n  router.get('/telemetry-config', async (c) => {\n    try {\n      const requestOrigin = new URL(c.req.url).origin;\n      return c.json(await resolveBrowserTelemetryConfig(process.env, requestOrigin));\n    } catch (error) {\n      return c.json(\n        {\n          enabled: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * GET /extension/tasks\n   * Extension polls for next task to execute\n   */\n  router.get('/tasks', (c) => {\n    try {\n      const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n      // Scoped by browser so two polling extensions cannot run each other's work.\n      const task = taskQueue.getNextTask(c.req.query('browserId'));\n\n      if (!task) {\n        return c.json<TaskPollResponse>({});\n      }\n\n      return c.json<TaskPollResponse>({\n        task: {\n          id: task.id,\n          tool: task.tool,\n          arguments: task.arguments,\n          telemetry: task.telemetry,\n        },\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * POST /extension/result\n   * Extension submits task execution result\n   */\n  router.post('/result', async (c) => {\n    try {\n      const body = (await c.req.json()) as ResultSubmitRequest;\n\n      if (!body.taskId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing taskId in request body',\n          },\n          400,\n        );\n      }\n\n      const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n      const result: ExtensionTaskResult = {\n        taskId: body.taskId,\n        success: body.success,\n        result: body.result,\n        error: body.error,\n      };\n\n      const task = taskQueue.submitResult(result);\n\n      if (!task) {\n        return c.json(\n          {\n            success: false,\n            error: `Task ${body.taskId} not found or already completed`,\n          },\n          404,\n        );\n      }\n\n      if (task.browserId) {\n        browserService.recordBrowserActivity(task.browserId, task.pageId);\n      }\n\n      return c.json({ success: true });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * GET /extension/status\n   * Connection health check and status\n   */\n  router.get('/status', (c) => {\n    try {\n      const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n      const status = taskQueue.getConnectionStatus();\n\n      const response: StatusResponse = {\n        connected: status.connected,\n        lastPollAt: status.lastPollAt?.toISOString(),\n        lastResultAt: status.lastResultAt?.toISOString(),\n        pendingTasks: status.pendingTasks,\n        queueSize: taskQueue.queueSize,\n      };\n\n      return c.json(response);\n    } catch (error) {\n      return c.json(\n        {\n          connected: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * POST /extension/register\n   * Extension registers a stealth browser session\n   */\n  router.post('/register', async (c) => {\n    try {\n      const body = (await c.req.json()) as SessionRegistrationRequest;\n\n      if (!body.browserId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing browserId in request body',\n          },\n          400,\n        );\n      }\n\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n      const session = sessionRegistry.register(body);\n\n      return c.json({\n        success: true,\n        session: {\n          id: session.id,\n          browserId: session.browserId,\n          controlMode: session.controlMode,\n          createdAt: session.createdAt.toISOString(),\n        },\n      });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * POST /extension/heartbeat\n   * Extension sends heartbeat to keep session alive\n   */\n  router.post('/heartbeat', async (c) => {\n    try {\n      const body = (await c.req.json()) as SessionHeartbeatRequest;\n\n      if (!body.sessionId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing sessionId in request body',\n          },\n          400,\n        );\n      }\n\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n      const session = sessionRegistry.heartbeat(body);\n\n      if (!session) {\n        return c.json(\n          {\n            success: false,\n            error: `Session ${body.sessionId} not found`,\n          },\n          404,\n        );\n      }\n\n      return c.json({\n        success: true,\n        session: {\n          id: session.id,\n          controlMode: session.controlMode,\n          handoffRequested: session.handoffRequested,\n          lastHeartbeatAt: session.lastHeartbeatAt.toISOString(),\n        },\n      });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  router.post('/tab-mapped', async (c) => {\n    try {\n      const body = (await c.req.json()) as TabMappedRequest;\n\n      if (!body.pageId || typeof body.tabId !== 'number') {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing pageId or tabId in request body',\n          },\n          400,\n        );\n      }\n\n      const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const pageEntry = pageRegistry.get(body.pageId);\n\n      if (!pageEntry) {\n        return c.json(\n          {\n            success: false,\n            error: `Page ${body.pageId} not found`,\n          },\n          404,\n        );\n      }\n\n      pageEntry.extensionTabId = body.tabId;\n      browserService.recordBrowserActivity(pageEntry.browserId, body.pageId);\n\n      return c.json({ success: true });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  router.post('/recording', RECORDING_BODY_LIMIT, async (c) => {\n    try {\n      const body = (await c.req.json()) as RecordingArtifactRequest;\n\n      if (!body.browserId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing browserId in request body',\n          },\n          400,\n        );\n      }\n\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const persisted = await browserService.persistExtensionRecordingArtifact(body.browserId, body.videoBase64);\n      emitExtensionRecordingDebug('artifact received', {\n        browserId: body.browserId,\n        videoBase64Size: body.videoBase64?.length ?? 0,\n        persisted,\n      });\n\n      if (!persisted) {\n        return c.json(\n          {\n            success: false,\n            error: `Browser \"${body.browserId}\" has no active recording target`,\n          },\n          404,\n        );\n      }\n\n      browserService.recordBrowserActivity(body.browserId);\n      return c.json({ success: true });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  router.post('/recording/chunk', RECORDING_CHUNK_BODY_LIMIT, async (c) => {\n    try {\n      const body = (await c.req.json()) as RecordingChunkRequest;\n\n      if (!body.browserId || !body.chunkBase64) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing browserId or chunkBase64 in request body',\n          },\n          400,\n        );\n      }\n\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const persisted = await browserService.persistExtensionRecordingChunk(body.browserId, body.chunkBase64);\n      emitExtensionRecordingDebug('chunk received', {\n        browserId: body.browserId,\n        chunkIndex: body.chunkIndex,\n        mimeType: body.mimeType,\n        persisted: !!persisted,\n        chunkBase64Size: body.chunkBase64.length,\n      });\n\n      if (!persisted) {\n        return c.json(\n          {\n            success: false,\n            error: `Browser \"${body.browserId}\" has no active recording target`,\n          },\n          404,\n        );\n      }\n\n      telemetry.log('debug', 'extension recording chunk received', {\n        attributes: {\n          'browse_tool.extension.recording.chunk_received': true,\n          'browse_tool.browser.id': body.browserId,\n          'browse_tool.recording.chunk_bytes': persisted.chunkBytes,\n          'browse_tool.recording.total_bytes': persisted.totalBytes,\n          'browse_tool.recording.chunk_count': persisted.chunkCount,\n          ...(typeof body.chunkIndex === 'number' ? { 'browse_tool.recording.chunk_index': body.chunkIndex } : {}),\n          ...(typeof body.mimeType === 'string' ? { 'browse_tool.recording.mime_type': body.mimeType } : {}),\n        },\n      });\n\n      browserService.recordBrowserActivity(body.browserId);\n      return c.json({ success: true });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  router.post('/browser-log', async (c) => {\n    try {\n      const body = (await c.req.json()) as BrowserLogRequest;\n\n      if (!body.message || typeof body.message !== 'string') {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing message in request body',\n          },\n          400,\n        );\n      }\n\n      telemetry.log(body.level ?? 'info', body.message, {\n        attributes: {\n          'browse_tool.extension.log_relay': true,\n          ...(typeof body.attributes === 'object' && body.attributes !== null ? body.attributes : {}),\n        },\n      });\n      emitExtensionRecordingDebug('browser log relayed', {\n        level: body.level ?? 'info',\n        message: body.message,\n        attributes: body.attributes,\n      });\n\n      return c.json({ success: true });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * POST /extension/handoff\n   * Request handoff from spec to AI control\n   */\n  router.post('/handoff', async (c) => {\n    try {\n      const body = (await c.req.json()) as HandoffRequest;\n\n      if (!body.sessionId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing sessionId in request body',\n          },\n          400,\n        );\n      }\n\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n      const session = sessionRegistry.requestHandoff(body);\n\n      if (!session) {\n        return c.json(\n          {\n            success: false,\n            error: `Session ${body.sessionId} not found`,\n          },\n          404,\n        );\n      }\n\n      return c.json({\n        success: true,\n        message: 'Handoff requested. AI will take control when ready.',\n        session: {\n          id: session.id,\n          controlMode: session.controlMode,\n          handoffRequested: session.handoffRequested,\n        },\n      });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * POST /extension/handoff/acknowledge\n   * AI acknowledges handoff and takes control\n   */\n  router.post('/handoff/acknowledge', async (c) => {\n    try {\n      const body = (await c.req.json()) as { sessionId: string };\n\n      if (!body.sessionId) {\n        return c.json(\n          {\n            success: false,\n            error: 'Missing sessionId in request body',\n          },\n          400,\n        );\n      }\n\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n      const session = sessionRegistry.acknowledgeHandoff(body.sessionId);\n\n      if (!session) {\n        return c.json(\n          {\n            success: false,\n            error: `Session ${body.sessionId} not found or no handoff pending`,\n          },\n          404,\n        );\n      }\n\n      return c.json({\n        success: true,\n        message: 'Handoff acknowledged. AI now has control.',\n        session: {\n          id: session.id,\n          controlMode: session.controlMode,\n          handoffRequested: session.handoffRequested,\n        },\n      });\n    } catch (error) {\n      return c.json(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * GET /extension/sessions\n   * List all active extension sessions\n   */\n  router.get('/sessions', (c) => {\n    try {\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n\n      const sessions = sessionRegistry.listSessions();\n\n      return c.json({\n        sessions: sessions.map((s) => ({\n          id: s.id,\n          browserId: s.browserId,\n          tabId: s.tabId,\n          currentUrl: s.currentUrl,\n          controlMode: s.controlMode,\n          activeSpecPath: s.activeSpecPath,\n          handoffRequested: s.handoffRequested,\n          createdAt: s.createdAt.toISOString(),\n          lastHeartbeatAt: s.lastHeartbeatAt.toISOString(),\n        })),\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  return router;\n}\n","/**\n * Chrome Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - HTTP server pattern for Chrome extension polling\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n */\n\nimport { DEFAULT_PORT_RANGE, PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n  createProcessLease,\n  type ProcessLease,\n  resolveSiblingRegistryPath,\n} from '@agimon-ai/foundation-process-registry';\nimport { serve } from '@hono/node-server';\nimport { Server } from '@modelcontextprotocol/server';\nimport { serveStdio } from '@modelcontextprotocol/server/stdio';\nimport { Command } from 'commander';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { Container, ContainerModule, type ContainerModuleLoadOptions } from 'inversify';\nimport 'reflect-metadata/lite';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createExtensionRoutes } from '../server/extension-routes.js';\nimport { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { ExtensionToolDelegator } from '../services/ExtensionToolDelegator.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { toMcpListTool } from '../utils/mcpToolDefinition.js';\nimport { resolveWorkspaceRoot } from '../utils/workspaceRoot.js';\n\nexport interface ChromeServeOptions {\n  port: number;\n  verbose: boolean;\n  waitForExtension: boolean;\n}\n\ninterface PortLease {\n  release(): Promise<void>;\n  port: number;\n}\n\nfunction parsePortValue(value: unknown): number | undefined {\n  if (value === undefined || value === null || value === '') {\n    return undefined;\n  }\n\n  const parsed =\n    typeof value === 'number' ? value : typeof value === 'string' ? Number.parseInt(value, 10) : Number.NaN;\n  if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {\n    throw new Error(`Invalid port: ${value}`);\n  }\n  return parsed;\n}\n\nconst SERVICE_NAME = 'browse-tool-chrome';\nconst SERVICE_TYPE = 'tool' as const;\nconst DEFAULT_ENVIRONMENT = process.env.NODE_ENV || 'development';\nconst DEFAULT_HOST = '127.0.0.1';\n\nfunction createPortRegistryService(): PortRegistryService {\n  return new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n}\n\nasync function createPortLease(\n  repositoryPath: string,\n  preferredPort: number,\n  portRange: { min: number; max: number },\n): Promise<PortLease> {\n  const portRegistry = createPortRegistryService();\n  const response = await portRegistry.reservePort({\n    repositoryPath,\n    serviceName: SERVICE_NAME,\n    serviceType: SERVICE_TYPE,\n    environment: DEFAULT_ENVIRONMENT,\n    preferredPort,\n    portRange,\n    pid: process.pid,\n    host: DEFAULT_HOST,\n    force: true,\n    metadata: { transport: 'stdio', mode: 'chrome-serve' },\n  });\n\n  if (!response.success || !response.record) {\n    throw new Error(response.error || `Failed to reserve port ${preferredPort}`);\n  }\n\n  let released = false;\n  return {\n    port: response.record.port,\n    release: async () => {\n      if (released) {\n        return;\n      }\n      released = true;\n\n      const releaseResponse = await portRegistry.releasePort({\n        repositoryPath,\n        serviceName: SERVICE_NAME,\n        serviceType: SERVICE_TYPE,\n        environment: DEFAULT_ENVIRONMENT,\n        pid: process.pid,\n      });\n\n      if (!releaseResponse.success && !releaseResponse.error?.includes('No matching registry entry')) {\n        throw new Error(releaseResponse.error || `Failed to release port ${preferredPort}`);\n      }\n    },\n  };\n}\n\n/**\n * Create extension-specific container module\n */\nfunction createExtensionModule(): ContainerModule {\n  return new ContainerModule((options: ContainerModuleLoadOptions) => {\n    options.bind(PLAYWRIGHT_TYPES.ExtensionTaskQueue).to(ExtensionTaskQueue).inSingletonScope();\n    options.bind(PLAYWRIGHT_TYPES.ExtensionToolDelegator).to(ExtensionToolDelegator).inSingletonScope();\n  });\n}\n\n/**\n * Create MCP server that delegates to Chrome extension\n */\nfunction createExtensionMcpServer(delegator: ExtensionToolDelegator): Server {\n  const server = new Server(\n    {\n      name: 'browse-tool-chrome',\n      version: '0.1.0',\n    },\n    {\n      capabilities: {\n        tools: {},\n      },\n    },\n  );\n\n  const supportedTools = delegator.getSupportedTools();\n\n  const toolDefinitions: ToolDefinition[] = supportedTools.map((name) => ({\n    name,\n    description: `Browser automation tool (Chrome extension mode): ${name}`,\n    inputSchema: {\n      type: 'object',\n      properties: {},\n      additionalProperties: true,\n    },\n  }));\n\n  server.setRequestHandler('tools/list', async () => {\n    return { tools: toolDefinitions.map(toMcpListTool) };\n  });\n\n  server.setRequestHandler('tools/call', async (request) => {\n    const { name, arguments: args } = request.params;\n    return await delegator.executeTool(name, args || {});\n  });\n\n  return server;\n}\n\nexport const chromeServeCommand = new Command('chrome-serve')\n  .description(\n    '[DEPRECATED] Start MCP server with Chrome extension HTTP polling for bot-detection-free browser automation',\n  )\n  .option('-p, --port <port>', 'HTTP server port for extension polling')\n  .option('-v, --verbose', 'Enable verbose output', false)\n  .option('--wait-for-extension', 'Wait for extension to connect before accepting MCP requests', false)\n  .action(async (options: ChromeServeOptions) => {\n    // Deprecation warning\n    console.error('');\n    console.error('╔════════════════════════════════════════════════════════════════╗');\n    console.error('║  DEPRECATED: chrome-serve is deprecated.                       ║');\n    console.error('║  Use mcp-serve instead - extension routes are now              ║');\n    console.error('║  automatically available in the HTTP server at /extension/*.   ║');\n    console.error('╚════════════════════════════════════════════════════════════════╝');\n    console.error('');\n\n    let portLease: PortLease | undefined;\n    let processLease: ProcessLease | undefined;\n\n    try {\n      const requestedPort = parsePortValue(options.port);\n      const repositoryPath = resolveWorkspaceRoot();\n      const portRegistryPath = process.env.PORT_REGISTRY_PATH;\n      if (portRegistryPath) {\n        process.env.PROCESS_REGISTRY_PATH = resolveSiblingRegistryPath(portRegistryPath, 'processes.json');\n      }\n      const reservationPort = requestedPort ?? DEFAULT_PORT_RANGE.min;\n      const reservationRange = requestedPort ? { min: requestedPort, max: requestedPort } : DEFAULT_PORT_RANGE;\n      portLease = await createPortLease(repositoryPath, reservationPort, reservationRange);\n      const leasedPort = portLease.port;\n      processLease = await createProcessLease({\n        repositoryPath,\n        serviceName: SERVICE_NAME,\n        serviceType: SERVICE_TYPE,\n        environment: DEFAULT_ENVIRONMENT,\n        pid: process.pid,\n        port: leasedPort,\n        host: DEFAULT_HOST,\n        command: process.argv[1],\n        args: process.argv.slice(2),\n        metadata: { transport: 'stdio', command: 'chrome-serve', waitForExtension: options.waitForExtension },\n      });\n\n      if (options.verbose) {\n        console.error('Chrome Extension MCP Server starting...');\n        console.error(`  HTTP Port: ${leasedPort}`);\n        console.error(`  Wait for extension: ${options.waitForExtension}`);\n      }\n\n      // Create container with extension services\n      const container = new Container({ defaultScope: 'Singleton' });\n      container.load(createExtensionModule());\n\n      // Get services\n      const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n      const delegator = container.get<ExtensionToolDelegator>(PLAYWRIGHT_TYPES.ExtensionToolDelegator);\n\n      // Create HTTP server for extension polling\n      const app = new Hono();\n\n      app.use(\n        '*',\n        cors({\n          origin: (origin) => origin ?? null,\n          credentials: true,\n          allowMethods: ['GET', 'POST', 'OPTIONS'],\n          allowHeaders: ['Content-Type', 'Accept'],\n        }),\n      );\n\n      // Mount extension routes\n      const extensionRoutes = createExtensionRoutes(container);\n      app.route('/extension', extensionRoutes);\n\n      // Health check\n      app.get('/health', (c) => {\n        const status = taskQueue.getConnectionStatus();\n        return c.json({\n          status: 'healthy',\n          service: 'browse-tool-chrome',\n          extension: status,\n        });\n      });\n\n      // Start HTTP server\n      const httpServer = serve({\n        fetch: app.fetch,\n        port: leasedPort,\n      });\n\n      // Handle server errors (e.g., port in use)\n      httpServer.on('error', (error: NodeJS.ErrnoException) => {\n        if (error.code === 'EADDRINUSE') {\n          console.error(`Error [PORT_IN_USE]: Port ${leasedPort} is already in use.`);\n          console.error('Recovery: Try a different port with --port <port>');\n        } else {\n          console.error(`Error [SERVER_ERROR]: HTTP server error: ${error.message}`);\n        }\n        process.exit(1);\n      });\n\n      console.error(`HTTP server started on port ${leasedPort}`);\n      console.error('Waiting for Chrome extension to connect...');\n      console.error(`Extension should poll: http://localhost:${leasedPort}/extension/tasks`);\n\n      // Wait for extension if requested\n      if (options.waitForExtension) {\n        console.error('Waiting for extension connection before starting MCP...');\n        await new Promise<void>((resolve) => {\n          const checkInterval = setInterval(() => {\n            const status = taskQueue.getConnectionStatus();\n            if (status.connected) {\n              clearInterval(checkInterval);\n              console.error('Extension connected!');\n              resolve();\n            }\n          }, 500);\n        });\n      }\n\n      // The explicit stdio entrypoint rejects legacy initialize requests.\n      const transport = serveStdio(() => createExtensionMcpServer(delegator), { legacy: 'reject' });\n\n      console.error('Chrome extension MCP server started on stdio');\n\n      const shutdown = async (signal: string) => {\n        console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n        try {\n          taskQueue.clearAllTasks('Server shutting down');\n          await transport.close();\n          httpServer.close();\n          await processLease!.release({ kill: false });\n          await portLease!.release();\n          process.exit(0);\n        } catch (error) {\n          console.error('Error during shutdown:', error);\n          process.exit(1);\n        }\n      };\n\n      process.on('SIGINT', () => shutdown('SIGINT'));\n      process.on('SIGTERM', () => shutdown('SIGTERM'));\n    } catch (error) {\n      if (processLease || portLease) {\n        try {\n          await processLease?.release();\n          await portLease?.release();\n        } catch {\n          // best effort cleanup\n        }\n      }\n      const errorMessage = error instanceof Error ? error.message : String(error);\n      console.error(`Error [SERVER_ERROR]: Failed to start Chrome extension MCP server: ${errorMessage}`);\n      console.error('Recovery: Check that the port is available and try again.');\n      process.exit(1);\n    }\n  });\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport path from 'node:path';\nimport type { Command } from 'commander';\nimport { z } from 'zod';\nimport { resolveWorkspaceRoot } from './utils/workspaceRoot.js';\n\nconst CONFIG_ENV_VAR = 'BROWSE_TOOL_CONFIG';\nconst CONFIG_DIR_NAME = '.browse-tool';\nconst CONFIG_FILE_NAME = 'config.json';\nconst OUTPUT_FORMATS = ['json', 'text', 'quiet'] as const;\nconst DEFAULT_CONFIG: BrowseToolConfig = {\n  commands: {},\n  tools: {},\n};\n\nconst toolDefaultsSchema = z.record(z.string(), z.unknown());\n\nconst commandDefaultsSchema = z.object({\n  mcpServe: z\n    .object({\n      type: z.string().optional(),\n      browser: z.string().optional(),\n      headless: z.boolean().optional(),\n      profile: z.string().optional(),\n      mode: z.string().optional(),\n      host: z.string().optional(),\n      port: z.coerce.number().int().positive().optional(),\n      httpPort: z.coerce.number().int().positive().optional(),\n      idleTimeout: z.coerce.number().positive().optional(),\n      tags: z.string().optional(),\n      exclude: z.string().optional(),\n      customTools: z.string().optional(),\n      chromeForTestingPath: z.string().optional(),\n      snippetsDir: z.string().optional(),\n      registryPath: z.string().optional(),\n      registryDir: z.string().optional(),\n      pidsDir: z.string().optional(),\n      profilesDir: z.string().optional(),\n      proxyConfigDir: z.string().optional(),\n    })\n    .partial()\n    .optional(),\n  httpServe: z\n    .object({\n      port: z.coerce.number().int().positive().optional(),\n      headless: z.boolean().optional(),\n      idleTimeout: z.coerce.number().positive().optional(),\n      host: z.string().optional(),\n      registryDir: z.string().optional(),\n      registryPath: z.string().optional(),\n      pidsDir: z.string().optional(),\n      profilesDir: z.string().optional(),\n      snippetsDir: z.string().optional(),\n      proxyConfigDir: z.string().optional(),\n      workspaceRoot: z.string().optional(),\n    })\n    .partial()\n    .optional(),\n  exec: z\n    .object({\n      format: z.enum(OUTPUT_FORMATS).optional(),\n      color: z.boolean().optional(),\n      port: z.coerce.number().int().positive().optional(),\n    })\n    .partial()\n    .optional(),\n  tools: z\n    .object({\n      format: z.enum(OUTPUT_FORMATS).optional(),\n      color: z.boolean().optional(),\n      port: z.coerce.number().int().positive().optional(),\n    })\n    .partial()\n    .optional(),\n  status: z.record(z.string(), z.unknown()).optional(),\n  stop: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst browseToolConfigSchema = z.object({\n  commands: commandDefaultsSchema.default({}),\n  tools: z.record(z.string(), toolDefaultsSchema).default({}),\n});\n\nexport type BrowseToolConfig = z.infer<typeof browseToolConfigSchema>;\n\nexport interface CliRuntimeContext {\n  config: BrowseToolConfig;\n  configPath?: string;\n}\n\nexport interface CliRuntimeOptions {\n  cwd?: string;\n  env?: NodeJS.ProcessEnv;\n  homeDir?: string;\n}\n\nlet runtimeContext: CliRuntimeContext = {\n  config: DEFAULT_CONFIG,\n};\n\nfunction extractExplicitConfigPath(argv: string[]): string | undefined {\n  for (let i = 0; i < argv.length; i += 1) {\n    const arg = argv[i];\n    if (arg === '--config') {\n      return argv[i + 1];\n    }\n    if (arg.startsWith('--config=')) {\n      return arg.slice('--config='.length);\n    }\n  }\n  return undefined;\n}\n\nfunction resolveConfigCandidatePath(candidatePath: string): string {\n  const resolvedPath = path.resolve(candidatePath);\n  if (!existsSync(resolvedPath)) {\n    throw new Error(`Config path not found: ${resolvedPath}`);\n  }\n\n  if (statSync(resolvedPath).isDirectory()) {\n    return path.join(resolvedPath, CONFIG_FILE_NAME);\n  }\n\n  return resolvedPath;\n}\n\nfunction findConfigPath(argv: string[], options: CliRuntimeOptions): string | undefined {\n  const env = options.env ?? process.env;\n  const cwd = options.cwd ?? resolveWorkspaceRoot({ argv, env, startPath: process.cwd() });\n  const homeDir = options.homeDir ?? homedir();\n\n  const explicitPath = extractExplicitConfigPath(argv);\n  if (explicitPath) {\n    return resolveConfigCandidatePath(explicitPath);\n  }\n\n  if (env[CONFIG_ENV_VAR]) {\n    return resolveConfigCandidatePath(env[CONFIG_ENV_VAR] as string);\n  }\n\n  const localConfigPath = path.join(cwd, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n  if (existsSync(localConfigPath)) {\n    return localConfigPath;\n  }\n\n  const homeConfigPath = path.join(homeDir, CONFIG_DIR_NAME, CONFIG_FILE_NAME);\n  if (existsSync(homeConfigPath)) {\n    return homeConfigPath;\n  }\n\n  return undefined;\n}\n\nfunction readConfigFile(configPath: string): BrowseToolConfig {\n  const content = readFileSync(configPath, 'utf8');\n  const parsed = JSON.parse(content) as unknown;\n  return browseToolConfigSchema.parse(parsed);\n}\n\nexport function loadCliRuntimeContext(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n  const configPath = findConfigPath(argv, options);\n  if (!configPath) {\n    return { config: DEFAULT_CONFIG };\n  }\n\n  return {\n    configPath,\n    config: readConfigFile(configPath),\n  };\n}\n\nexport function initializeCliRuntime(argv: string[], options: CliRuntimeOptions = {}): CliRuntimeContext {\n  runtimeContext = loadCliRuntimeContext(argv, options);\n  return runtimeContext;\n}\n\nexport function getCliRuntimeContext(): CliRuntimeContext {\n  return runtimeContext;\n}\n\nexport function setCliRuntimeContextForTesting(context: CliRuntimeContext): void {\n  runtimeContext = context;\n}\n\nexport function getCommandConfig<T extends Record<string, unknown>>(\n  commandName: keyof BrowseToolConfig['commands'],\n): T {\n  const commands = runtimeContext.config.commands ?? {};\n  return (commands[commandName] ?? {}) as T;\n}\n\nexport function getToolConfig(toolName: string): Record<string, unknown> {\n  return runtimeContext.config.tools?.[toolName] ?? {};\n}\n\nexport function resolveConfiguredOption<T>(\n  command: Command,\n  optionName: string,\n  currentValue: T,\n  configValue?: T,\n  envValue?: T,\n): T {\n  const source = command.getOptionValueSourceWithGlobals(optionName);\n  if (source !== undefined && source !== 'default' && source !== 'implied') {\n    return currentValue;\n  }\n\n  if (envValue !== undefined) {\n    return envValue;\n  }\n\n  if (configValue !== undefined) {\n    return configValue;\n  }\n\n  return currentValue;\n}\n","/**\n * HTTP Client Utility for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Service pattern for HTTP communication\n * - Dependency injection via container\n * - Error handling with descriptive messages\n *\n * CODING STANDARDS:\n * - Use async/await for all HTTP operations\n * - Return typed responses\n * - Handle connection errors gracefully\n *\n * AVOID:\n * - Hardcoded URLs (use server discovery)\n * - Missing error handling\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createMcpContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { OWNER_HEADER, resolveOwnerId } from './ownerContext.js';\nimport { resolveTelemetryContextFromEnv, telemetryContextToHeaders } from './telemetryContext.js';\n\n/**\n * Response from /tools endpoint\n */\ninterface ToolsResponse {\n  tools: ToolDefinition[];\n  error?: string;\n}\n\nexport interface CustomToolDefinition {\n  name: string;\n  description: string;\n  suggestionActions?: string;\n  inputSchema: ToolDefinition['inputSchema'];\n  capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n  tools: CustomToolDefinition[];\n  error?: string;\n}\n\n/**\n * Browser info response from /browsers\n */\nexport interface BrowserInfo {\n  id: string;\n  profileName?: string;\n  pageIds: string[];\n  currentPageId?: string;\n  createdAt: string;\n}\n\ninterface BrowsersResponse {\n  browsers: BrowserInfo[];\n  error?: string;\n}\n\n/**\n * Response from /execute endpoint\n */\ninterface ExecuteResponse {\n  success: boolean;\n  result?: CallToolResult;\n  error?: string;\n}\n\n/**\n * Client options\n */\nexport interface ToolClientOptions {\n  /** Port to use for HTTP server */\n  port?: number;\n  /** When true, only connect to or spawn the exact requested port */\n  exactPort?: boolean;\n  /** Owner id used to scope direct CLI calls */\n  ownerId?: string;\n  /** Timeout for HTTP requests in milliseconds */\n  timeout?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60000;\n\n/**\n * HTTP client for executing tools via the browse-tool HTTP server.\n * Handles server discovery/startup and provides typed methods for tool operations.\n */\nexport class ToolClient {\n  private readonly port: number;\n  private readonly exactPort: boolean;\n  private readonly ownerId: string;\n  private readonly timeout: number;\n  /** Agent/workflow identity for this client, so the shared daemon can attribute\n   * its logs to us rather than to whichever agent spawned it. */\n  private readonly telemetryHeaders: Record<string, string>;\n  private serverPort: number | null = null;\n\n  constructor(options: ToolClientOptions = {}) {\n    this.port = options.port ?? DEFAULT_MCP_PORT;\n    this.exactPort = options.exactPort ?? false;\n    this.ownerId = resolveOwnerId({ ownerId: options.ownerId });\n    this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n    this.telemetryHeaders = telemetryContextToHeaders(resolveTelemetryContextFromEnv());\n  }\n\n  /**\n   * Ensure HTTP server is running and return its port\n   */\n  private async ensureServer(): Promise<number> {\n    if (this.serverPort !== null) {\n      return this.serverPort;\n    }\n\n    const container = createMcpContainer();\n    const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n    const status = await httpServerManager.ensureRunning(this.port, { exactPort: this.exactPort });\n\n    if (!status.running || !status.port) {\n      throw new Error(status.error ?? 'Failed to start HTTP server. Try running \"browse-tool http-serve\" manually.');\n    }\n\n    this.serverPort = status.port;\n    return status.port;\n  }\n\n  /**\n   * Get base URL for HTTP server\n   */\n  private async getBaseUrl(): Promise<string> {\n    const port = await this.ensureServer();\n    return `http://localhost:${port}`;\n  }\n\n  private jsonHeaders(): Record<string, string> {\n    return {\n      'Content-Type': 'application/json',\n      Accept: 'application/json',\n      [OWNER_HEADER]: this.ownerId,\n      ...this.telemetryHeaders,\n    };\n  }\n\n  /**\n   * List all available tools from the HTTP server\n   */\n  async listTools(): Promise<ToolDefinition[]> {\n    const baseUrl = await this.getBaseUrl();\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n    try {\n      const response = await fetch(`${baseUrl}/tools`, {\n        method: 'GET',\n        headers: {\n          Accept: 'application/json',\n        },\n        signal: controller.signal,\n      });\n\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n      }\n\n      const data = (await response.json()) as ToolsResponse;\n      if (data.error) {\n        throw new Error(data.error);\n      }\n      return data.tools;\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n      }\n      throw this.wrapConnectionError(error);\n    } finally {\n      clearTimeout(timeoutId);\n    }\n  }\n\n  /**\n   * List custom tools exposed by the HTTP server for a given directory\n   */\n  async listCustomTools(directory: string): Promise<CustomToolDefinition[]> {\n    const baseUrl = await this.getBaseUrl();\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n    try {\n      const url = new URL('/custom-tools', baseUrl);\n      url.searchParams.set('dir', directory);\n\n      const response = await fetch(url, {\n        method: 'GET',\n        headers: {\n          Accept: 'application/json',\n        },\n        signal: controller.signal,\n      });\n\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n      }\n\n      const data = (await response.json()) as CustomToolsResponse;\n      if (data.error) {\n        throw new Error(data.error);\n      }\n\n      return data.tools;\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n      }\n      throw this.wrapConnectionError(error);\n    } finally {\n      clearTimeout(timeoutId);\n    }\n  }\n\n  /**\n   * List active browsers from the HTTP server\n   */\n  async listBrowsers(): Promise<BrowserInfo[]> {\n    const baseUrl = await this.getBaseUrl();\n\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n    try {\n      const response = await fetch(`${baseUrl}/browsers`, {\n        method: 'GET',\n        headers: {\n          Accept: 'application/json',\n        },\n        signal: controller.signal,\n      });\n\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n      }\n\n      const data = (await response.json()) as BrowsersResponse;\n      if (data.error) {\n        throw new Error(data.error);\n      }\n\n      return data.browsers;\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n      }\n      throw this.wrapConnectionError(error);\n    } finally {\n      clearTimeout(timeoutId);\n    }\n  }\n\n  /**\n   * Execute a tool with the given arguments\n   */\n  async execute(tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n    const baseUrl = await this.getBaseUrl();\n    // run_spec can execute long-running suites. Do not enforce client-side request timeout.\n    const timeoutMs = tool === 'run_spec' ? undefined : this.timeout;\n    const controller = new AbortController();\n    const timeoutId = timeoutMs !== undefined ? setTimeout(() => controller.abort(), timeoutMs) : undefined;\n\n    try {\n      const response = await fetch(`${baseUrl}/execute`, {\n        method: 'POST',\n        headers: this.jsonHeaders(),\n        body: JSON.stringify({\n          tool,\n          arguments: args,\n        }),\n        signal: timeoutMs !== undefined ? controller.signal : undefined,\n      });\n\n      if (!response.ok) {\n        const text = await response.text();\n        throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n      }\n\n      const data = (await response.json()) as ExecuteResponse;\n\n      if (!data.success) {\n        return {\n          content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n          isError: true,\n        };\n      }\n\n      return (\n        data.result ?? {\n          content: [{ type: 'text', text: 'No result returned' }],\n        }\n      );\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n      }\n      throw this.wrapConnectionError(error);\n    } finally {\n      if (timeoutId) {\n        clearTimeout(timeoutId);\n      }\n    }\n  }\n\n  /**\n   * Execute a custom tool exposed by the HTTP server for a given directory\n   */\n  async executeCustomTool(directory: string, tool: string, args: Record<string, unknown>): Promise<CallToolResult> {\n    const baseUrl = await this.getBaseUrl();\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n    try {\n      const url = new URL('/custom-tools', baseUrl);\n      url.searchParams.set('dir', directory);\n\n      const response = await fetch(url, {\n        method: 'POST',\n        headers: this.jsonHeaders(),\n        body: JSON.stringify({\n          tool,\n          arguments: args,\n        }),\n        signal: controller.signal,\n      });\n\n      if (!response.ok) {\n        const text = await response.text();\n        throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);\n      }\n\n      const data = (await response.json()) as ExecuteResponse;\n\n      if (!data.success) {\n        return {\n          content: [{ type: 'text', text: data.error ?? 'Unknown error' }],\n          isError: true,\n        };\n      }\n\n      return (\n        data.result ?? {\n          content: [{ type: 'text', text: 'No result returned' }],\n        }\n      );\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`, { cause: error });\n      }\n      throw this.wrapConnectionError(error);\n    } finally {\n      clearTimeout(timeoutId);\n    }\n  }\n\n  /**\n   * Wrap connection errors with helpful recovery messages\n   */\n  private wrapConnectionError(error: unknown): Error {\n    if (error instanceof Error) {\n      if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n        return new Error(\n          `Cannot connect to browse-tool HTTP server.\\n\\nTry one of the following:\\n  1. Start the server: browse-tool http-serve\\n  2. Check if another process is using port ${this.port}\\n  3. Run with a different port: browse-tool --port 3201 <command>\\n\\nOriginal error: ${error.message}`,\n          { cause: error },\n        );\n      }\n      return error;\n    }\n    return new Error(String(error), { cause: error });\n  }\n}\n\n/**\n * Create a new ToolClient instance\n */\nexport function createToolClient(options?: ToolClientOptions): ToolClient {\n  return new ToolClient(options);\n}\n","/**\n * Output Formatter Utilities for CLI Tool Commands\n *\n * DESIGN PATTERNS:\n * - Pure functions with no side effects\n * - Single responsibility per function\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - External dependencies (keep utilities pure)\n */\n\nimport type { CallToolResult, ImageContent, TextContent } from '@modelcontextprotocol/server';\n\n/**\n * Output format options\n */\nexport type OutputFormat = 'json' | 'text' | 'quiet';\n\n/**\n * Formatter options\n */\nexport interface FormatterOptions {\n  format: OutputFormat;\n  color: boolean;\n}\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n  reset: '\\x1b[0m',\n  red: '\\x1b[31m',\n  green: '\\x1b[32m',\n  yellow: '\\x1b[33m',\n  blue: '\\x1b[34m',\n  magenta: '\\x1b[35m',\n  cyan: '\\x1b[36m',\n  gray: '\\x1b[90m',\n  bold: '\\x1b[1m',\n} as const;\n\n/**\n * Apply color to text if color is enabled\n */\nexport function colorize(text: string, color: keyof typeof COLORS, useColor: boolean): string {\n  if (!useColor) {\n    return text;\n  }\n  return `${COLORS[color]}${text}${COLORS.reset}`;\n}\n\n/**\n * Format a CallToolResult for CLI output\n */\nexport function formatToolResult(result: CallToolResult, options: FormatterOptions): string {\n  const { format, color } = options;\n\n  if (format === 'quiet') {\n    return '';\n  }\n\n  if (format === 'json') {\n    return formatAsJson(result);\n  }\n\n  return formatAsText(result, color);\n}\n\n/**\n * Format result as JSON\n */\nexport function formatAsJson(result: CallToolResult): string {\n  return JSON.stringify(result, null, 2);\n}\n\n/**\n * Format result as human-readable text\n */\nexport function formatAsText(result: CallToolResult, useColor: boolean): string {\n  const lines: string[] = [];\n\n  if (result.isError) {\n    lines.push(colorize('Error:', 'red', useColor));\n  }\n\n  for (const content of result.content) {\n    if (content.type === 'text') {\n      lines.push(formatTextContent(content as TextContent, useColor));\n    } else if (content.type === 'image') {\n      lines.push(formatImageContent(content as ImageContent, useColor));\n    } else {\n      lines.push(colorize(`[Unknown content type: ${content.type}]`, 'yellow', useColor));\n    }\n  }\n\n  return lines.join('\\n');\n}\n\n/**\n * Format text content\n */\nexport function formatTextContent(content: TextContent, useColor: boolean): string {\n  const text = content.text;\n\n  // Try to parse as JSON for pretty printing\n  try {\n    const parsed = JSON.parse(text);\n    return formatParsedJson(parsed, useColor);\n  } catch {\n    // Not JSON, return as-is\n    return text;\n  }\n}\n\n/**\n * Format parsed JSON object with optional coloring\n */\nexport function formatParsedJson(data: unknown, useColor: boolean): string {\n  if (typeof data !== 'object' || data === null) {\n    return String(data);\n  }\n\n  const lines: string[] = [];\n\n  for (const [key, value] of Object.entries(data)) {\n    const coloredKey = colorize(key, 'cyan', useColor);\n    const formattedValue = formatValue(value, useColor);\n    lines.push(`${coloredKey}: ${formattedValue}`);\n  }\n\n  return lines.join('\\n');\n}\n\n/**\n * Format a value with type-appropriate coloring\n */\nexport function formatValue(value: unknown, useColor: boolean): string {\n  if (value === null) {\n    return colorize('null', 'gray', useColor);\n  }\n\n  if (value === undefined) {\n    return colorize('undefined', 'gray', useColor);\n  }\n\n  if (typeof value === 'boolean') {\n    return colorize(String(value), value ? 'green' : 'red', useColor);\n  }\n\n  if (typeof value === 'number') {\n    return colorize(String(value), 'yellow', useColor);\n  }\n\n  if (typeof value === 'string') {\n    // Check if it's a URL\n    if (value.startsWith('http://') || value.startsWith('https://')) {\n      return colorize(value, 'blue', useColor);\n    }\n    return value;\n  }\n\n  if (Array.isArray(value)) {\n    if (value.length === 0) {\n      return colorize('[]', 'gray', useColor);\n    }\n    return JSON.stringify(value, null, 2);\n  }\n\n  if (typeof value === 'object') {\n    return JSON.stringify(value, null, 2);\n  }\n\n  return String(value);\n}\n\n/**\n * Format image content\n */\nexport function formatImageContent(content: ImageContent, useColor: boolean): string {\n  const { mimeType, data } = content;\n  const sizeKb = Math.round((data.length * 3) / 4 / 1024);\n\n  return colorize(`[Image: ${mimeType}, ~${sizeKb}KB base64 data]`, 'magenta', useColor);\n}\n\n/**\n * Format an error for CLI output\n */\nexport function formatError(error: Error | string, useColor: boolean): string {\n  const message = error instanceof Error ? error.message : error;\n  return colorize(`Error: ${message}`, 'red', useColor);\n}\n\n/**\n * Format a success message for CLI output\n */\nexport function formatSuccess(message: string, useColor: boolean): string {\n  return colorize(`✓ ${message}`, 'green', useColor);\n}\n\n/**\n * Format a warning message for CLI output\n */\nexport function formatWarning(message: string, useColor: boolean): string {\n  return colorize(`⚠ ${message}`, 'yellow', useColor);\n}\n\n/**\n * Format a list of tools for help output\n */\nexport function formatToolList(tools: Array<{ name: string; description: string }>, useColor: boolean): string {\n  const maxNameLength = Math.max(...tools.map((t) => t.name.length));\n  const lines: string[] = [];\n\n  for (const tool of tools) {\n    const paddedName = tool.name.padEnd(maxNameLength);\n    const coloredName = colorize(paddedName, 'cyan', useColor);\n    lines.push(`  ${coloredName}  ${tool.description}`);\n  }\n\n  return lines.join('\\n');\n}\n","import { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport {\n  type FormatterOptions,\n  formatError,\n  formatToolList,\n  formatToolResult,\n  type OutputFormat,\n} from '../utils/outputFormatter.js';\n\ninterface CustomToolsCommandOptions {\n  format: OutputFormat;\n  color: boolean;\n  port: string;\n  owner?: string;\n}\n\nfunction resolveCommonOptions(\n  command: Command,\n  options: CustomToolsCommandOptions,\n): {\n  port: string;\n  formatterOptions: FormatterOptions;\n} {\n  const commandDefaults = getCommandConfig<{\n    format?: OutputFormat;\n    color?: boolean;\n    port?: number;\n  }>('tools');\n\n  const format = resolveConfiguredOption(command, 'format', options.format as OutputFormat, commandDefaults.format);\n  const color = resolveConfiguredOption(command, 'color', options.color, commandDefaults.color);\n  const port = resolveConfiguredOption(\n    command,\n    'port',\n    options.port,\n    commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n    process.env.PLAYWRIGHT_PORT,\n  );\n\n  return {\n    port,\n    formatterOptions: {\n      format,\n      color,\n    },\n  };\n}\n\nexport const listCustomToolsCommand = new Command('list-custom-tools')\n  .description('List custom tools from a tools directory')\n  .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n  .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n  .option('--no-color', 'Disable colored output')\n  .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n  .action(async function (this: Command, dir: string, options: CustomToolsCommandOptions) {\n    const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n    try {\n      // Listing custom tools is directory-scoped, not owner-scoped, so no owner is sent.\n      const client = createToolClient({\n        port: Number.parseInt(port, 10),\n      });\n\n      const tools = await client.listCustomTools(dir);\n      const output =\n        formatterOptions.format === 'text'\n          ? formatToolList(tools, formatterOptions.color)\n          : formatterOptions.format === 'quiet'\n            ? ''\n            : JSON.stringify(tools, null, 2);\n\n      if (output) {\n        console.log(output);\n      }\n    } catch (error) {\n      console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n      process.exit(1);\n    }\n  });\n\nexport const execCustomToolCommand = new Command('exec-custom-tool')\n  .description('Execute a custom tool from a tools directory')\n  .argument('<dir>', 'Path to the tools directory containing tools.yaml')\n  .argument('<tool>', 'Custom tool name to execute')\n  .argument('[args]', 'JSON arguments for the tool', '{}')\n  .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n  .option('--no-color', 'Disable colored output')\n  .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n  .option('--owner <owner>', 'Owner id used to scope browser resources for direct CLI calls')\n  .action(async function (\n    this: Command,\n    dir: string,\n    tool: string,\n    argsJson: string,\n    options: CustomToolsCommandOptions,\n  ) {\n    const { port, formatterOptions } = resolveCommonOptions(this, options);\n\n    try {\n      let args: Record<string, unknown>;\n      try {\n        args = JSON.parse(argsJson);\n      } catch {\n        console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n        process.exit(1);\n        return;\n      }\n\n      const client = createToolClient({\n        port: Number.parseInt(port, 10),\n        ownerId: options.owner,\n      });\n\n      const result = await client.executeCustomTool(dir, tool, args);\n      const output = formatToolResult(result, formatterOptions);\n      if (output) {\n        console.log(output);\n      }\n\n      if (result.isError) {\n        process.exit(1);\n      }\n    } catch (error) {\n      console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n      process.exit(1);\n    }\n  });\n\nexport const customToolsCommand = new Command('custom-tools')\n  .description('List and execute custom tools through the HTTP server')\n  .addCommand(listCustomToolsCommand)\n  .addCommand(execCustomToolCommand);\n","import { Command } from 'commander';\nimport chromeForTestingConfig from '../config/chrome-for-testing.json';\nimport {\n  buildDockerChromeForTestingImage,\n  resolveChromeForTestingArchivePlatform,\n} from '../utils/dockerChromeForTesting.js';\n\ninterface DockerBuildCftOptions {\n  cftVersion?: string;\n  image?: string;\n  platform?: string;\n}\n\nexport const dockerBuildCftCommand = new Command('docker-build-cft')\n  .description('Build the Chrome for Testing Docker image used by vm mode')\n  .option('--cft-version <version>', 'Chrome for Testing version to build', chromeForTestingConfig.version)\n  .option('--image <image>', 'Docker image tag to produce', chromeForTestingConfig.dockerImage)\n  .option('--platform <platform>', 'Docker target platform', chromeForTestingConfig.dockerPlatform)\n  .action(async (options: DockerBuildCftOptions) => {\n    try {\n      const result = await buildDockerChromeForTestingImage({\n        version: options.cftVersion,\n        image: options.image,\n        platform: options.platform,\n        stdio: 'inherit',\n      });\n\n      console.log(`Built Docker image ${result.image}`);\n      console.log(`  Version: ${result.version}`);\n      console.log(`  Platform: ${result.platform}`);\n      console.log(`  Archive: ${resolveChromeForTestingArchivePlatform(result.platform)}`);\n    } catch (error) {\n      console.error(error instanceof Error ? error.message : String(error));\n      process.exit(1);\n    }\n  });\n","/**\n * Exec Command\n *\n * Execute a tool directly with JSON arguments. Power user command for raw API access.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() and .argument() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createToolClient } from '../utils/httpClient.js';\nimport { DEFAULT_MCP_PORT } from '../utils/networkConfig.js';\nimport { type FormatterOptions, formatError, formatToolResult, type OutputFormat } from '../utils/outputFormatter.js';\n\ninterface ExecOptions {\n  format: OutputFormat;\n  color: boolean;\n  port: string;\n  owner?: string;\n}\n\n/**\n * Execute a tool directly with JSON arguments\n */\nexport const execCommand = new Command('exec')\n  .description('Execute a tool directly with JSON arguments')\n  .argument('<tool>', 'Tool name to execute (e.g., browser_launch)')\n  .argument('[args]', 'JSON arguments for the tool', '{}')\n  .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n  .option('--no-color', 'Disable colored output')\n  .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT))\n  .option('--owner <owner>', 'Owner id used to scope browser resources for direct CLI calls')\n  .action(async function (this: Command, tool: string, argsJson: string, options: ExecOptions) {\n    const commandDefaults = getCommandConfig<{\n      format?: OutputFormat;\n      color?: boolean;\n      port?: number;\n    }>('exec');\n    const format = resolveConfiguredOption(this, 'format', options.format as OutputFormat, commandDefaults.format);\n    const color = resolveConfiguredOption(this, 'color', options.color, commandDefaults.color);\n    const port = resolveConfiguredOption(\n      this,\n      'port',\n      options.port,\n      commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n      process.env.PLAYWRIGHT_PORT,\n    );\n    const portSource = this.getOptionValueSourceWithGlobals('port');\n    const exactPort = portSource !== undefined && portSource !== 'default' && portSource !== 'implied';\n    const formatterOptions: FormatterOptions = {\n      format,\n      color,\n    };\n\n    try {\n      // Parse JSON arguments\n      let args: Record<string, unknown>;\n      try {\n        args = JSON.parse(argsJson);\n      } catch {\n        console.error(formatError(`Invalid JSON arguments: ${argsJson}`, formatterOptions.color));\n        process.exit(1);\n      }\n\n      // Create client and execute\n      const client = createToolClient({\n        port: Number.parseInt(port, 10),\n        exactPort,\n        ownerId: options.owner,\n      });\n\n      const result = await client.execute(tool, args);\n\n      // Format and output result\n      const output = formatToolResult(result, formatterOptions);\n      if (output) {\n        console.log(output);\n      }\n\n      // Exit with error code if tool failed\n      if (result.isError) {\n        process.exit(1);\n      }\n    } catch (error) {\n      console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n      process.exit(1);\n    }\n  });\n","import { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport type { Attributes } from '@opentelemetry/api';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { transform } from 'esbuild';\nimport { z } from 'zod';\nimport { assertPageOwnership, getCurrentOwnerId } from '../utils/ownerContext.js';\nimport type { BrowserMode, IBrowserService } from './BrowserService.js';\nimport { ExtensionPageProxy, type IExtensionPageProxy } from './ExtensionPageProxy.js';\nimport type { ExtensionTaskQueue } from './ExtensionTaskQueue.js';\nimport type { IPageRegistry, PageEntry } from './PageRegistry.js';\nimport { type ITelemetryService, TelemetryService } from './TelemetryService.js';\n\nconst MANIFEST_FILE = 'tools.yaml';\nconst REQUIRED_PAGE_ID_FIELD = 'pageId';\nconst OPTIONAL_BROWSER_ID_FIELD = 'browserId';\n\nconst ToolCapabilitiesSchema = z.record(z.string(), z.unknown());\n\nconst ToolInputSchemaSchema = z\n  .object({\n    type: z.literal('object'),\n    properties: z.record(z.string(), z.unknown()).optional(),\n    required: z.array(z.string()).optional(),\n    additionalProperties: z.boolean().optional(),\n  })\n  .passthrough();\n\nconst CustomToolManifestEntrySchema = z.object({\n  name: z.string().min(1),\n  description: z.string().min(1),\n  script: z.string().min(1),\n  suggestionActions: z.string().min(1).optional(),\n  capabilities: ToolCapabilitiesSchema,\n  inputSchema: ToolInputSchemaSchema,\n});\n\nconst CustomToolManifestSchema = z.object({\n  tools: z.array(CustomToolManifestEntrySchema),\n});\n\ntype CustomToolManifestEntry = z.infer<typeof CustomToolManifestEntrySchema>;\n\nexport interface CustomToolDefinition {\n  name: string;\n  description: string;\n  suggestionActions?: string;\n  inputSchema: z.infer<typeof ToolInputSchemaSchema>;\n  capabilities: z.infer<typeof ToolCapabilitiesSchema>;\n}\n\ninterface LoadedCustomTool extends CustomToolDefinition {\n  scriptPath: string;\n  execute?: CustomToolHandler;\n}\n\ntype CustomToolPage = NonNullable<PageEntry['page']> | IExtensionPageProxy;\n\nexport interface CustomToolBrowserPageSummary {\n  pageId: string;\n  url: string;\n  title: string;\n  active: boolean;\n}\n\nexport interface CustomToolBrowserPageHandle extends CustomToolBrowserPageSummary {\n  page: CustomToolPage;\n}\n\nexport interface CustomToolBrowser {\n  readonly browserId: string;\n  readonly mode: BrowserMode;\n  listPages(): Promise<CustomToolBrowserPageSummary[]>;\n  getPage(pageId: string): Promise<CustomToolBrowserPageHandle>;\n  getCurrentPage(): Promise<CustomToolBrowserPageHandle>;\n  newPage(options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle>;\n}\n\nexport interface CustomToolLogOptions {\n  attributes?: Record<string, unknown>;\n  exception?: unknown;\n}\n\nexport interface CustomToolLogger {\n  getTraceContext(): { traceId?: string; spanId?: string };\n  trace(message: string, options?: CustomToolLogOptions): void;\n  debug(message: string, options?: CustomToolLogOptions): void;\n  info(message: string, options?: CustomToolLogOptions): void;\n  warn(message: string, options?: CustomToolLogOptions): void;\n  error(message: string, options?: CustomToolLogOptions): void;\n  fatal(message: string, options?: CustomToolLogOptions): void;\n}\n\ntype CustomToolHandler = (context: {\n  page: CustomToolPage;\n  browser: CustomToolBrowser;\n  input: Record<string, unknown>;\n  logger: CustomToolLogger;\n}) => unknown;\n\ninterface LoadedCustomToolModule {\n  run?: unknown;\n  default?: unknown;\n}\n\ninterface YamlLine {\n  indent: number;\n  text: string;\n}\n\nconst CUSTOM_TOOL_DEBUG_ENABLED = process.env.BROWSE_TOOL_DEBUG_CUSTOM_TOOLS === '1';\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS = 1500;\nconst CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS = 50;\n\nfunction summarizeValue(value: unknown): string {\n  return JSON.stringify(value, (_key, current) => {\n    if (typeof current === 'string' && current.length > 240) {\n      return `${current.slice(0, 240)}...<trimmed>`;\n    }\n    return current;\n  });\n}\n\nfunction debugCustomTool(message: string, details?: Record<string, unknown>): void {\n  if (!CUSTOM_TOOL_DEBUG_ENABLED) {\n    return;\n  }\n\n  if (details) {\n    console.error(`[CustomToolService] ${message}`, details);\n    return;\n  }\n\n  console.error(`[CustomToolService] ${message}`);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isCallToolResult(value: unknown): value is CallToolResult {\n  return isPlainObject(value) && Array.isArray(value.content);\n}\n\nfunction normalizeScalar(rawValue: string): unknown {\n  const value = rawValue.trim();\n  if (value === '') {\n    return '';\n  }\n  if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith('[') && value.endsWith(']'))) {\n    return JSON.parse(value);\n  }\n  if (value.startsWith('{') && value.endsWith('}')) {\n    return JSON.parse(value);\n  }\n  if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n    return value.slice(1, -1);\n  }\n  if (value === 'true') {\n    return true;\n  }\n  if (value === 'false') {\n    return false;\n  }\n  if (value === 'null') {\n    return null;\n  }\n  if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n    return Number(value);\n  }\n  return value;\n}\n\nfunction toAttributes(input: Record<string, unknown> | undefined): Attributes | undefined {\n  if (!input || Object.keys(input).length === 0) {\n    return undefined;\n  }\n\n  const attributes: Attributes = {};\n  for (const [key, value] of Object.entries(input)) {\n    if (value === undefined || value === null) {\n      continue;\n    }\n\n    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n      attributes[key] = value;\n      continue;\n    }\n\n    attributes[key] = JSON.stringify(value);\n  }\n\n  return Object.keys(attributes).length > 0 ? attributes : undefined;\n}\n\nfunction toYamlLines(input: string): YamlLine[] {\n  return input\n    .replace(/^\\uFEFF/, '')\n    .split(/\\r?\\n/)\n    .map((line) => {\n      if (line.includes('\\t')) {\n        throw new Error('Tab indentation is not supported in tools.yaml');\n      }\n\n      const withoutComment = line.replace(/\\s+#.*$/, '');\n      if (withoutComment.trim().length === 0) {\n        return null;\n      }\n\n      const indent = withoutComment.match(/^ */)?.[0].length ?? 0;\n      return {\n        indent,\n        text: withoutComment.trim(),\n      };\n    })\n    .filter((line): line is YamlLine => line !== null);\n}\n\nfunction parseYamlValue(lines: YamlLine[], startIndex: number, indent: number): [unknown, number] {\n  const line = lines[startIndex];\n  if (!line || line.indent !== indent) {\n    throw new Error(`Invalid indentation in tools.yaml at line ${startIndex + 1}`);\n  }\n\n  if (line.text.startsWith('-')) {\n    return parseYamlArray(lines, startIndex, indent);\n  }\n\n  return parseYamlObject(lines, startIndex, indent);\n}\n\nfunction parseYamlArray(lines: YamlLine[], startIndex: number, indent: number): [unknown[], number] {\n  const result: unknown[] = [];\n  let index = startIndex;\n\n  while (index < lines.length) {\n    const line = lines[index];\n    if (line.indent < indent) {\n      break;\n    }\n    if (line.indent !== indent || !line.text.startsWith('-')) {\n      break;\n    }\n\n    const itemText = line.text.slice(1).trim();\n    if (itemText === '') {\n      const nextLine = lines[index + 1];\n      if (!nextLine || nextLine.indent <= indent) {\n        result.push(null);\n        index += 1;\n        continue;\n      }\n      const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n      result.push(nestedValue);\n      index = nextIndex;\n      continue;\n    }\n\n    if (itemText.includes(':')) {\n      const [key, rawValue] = splitYamlKeyValue(itemText);\n      const objectValue: Record<string, unknown> = {};\n\n      if (rawValue === undefined) {\n        const nextLine = lines[index + 1];\n        if (!nextLine || nextLine.indent <= indent) {\n          throw new Error(`Expected nested value for \"${key}\" in tools.yaml`);\n        }\n        const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n        objectValue[key] = nestedValue;\n        index = nextIndex;\n      } else {\n        objectValue[key] = normalizeScalar(rawValue);\n        index += 1;\n      }\n\n      while (index < lines.length && lines[index].indent > indent) {\n        const nestedLine = lines[index];\n        if (nestedLine.indent !== indent + 2 || nestedLine.text.startsWith('-')) {\n          const [nestedValue, nextIndex] = parseYamlValue(lines, index, nestedLine.indent);\n          if (!isPlainObject(nestedValue)) {\n            throw new Error(`Expected object entry in tools.yaml at line ${index + 1}`);\n          }\n          Object.assign(objectValue, nestedValue);\n          index = nextIndex;\n          continue;\n        }\n\n        const [nestedKey, nestedRawValue] = splitYamlKeyValue(nestedLine.text);\n        if (nestedRawValue === undefined) {\n          const nextLine = lines[index + 1];\n          if (!nextLine || nextLine.indent <= nestedLine.indent) {\n            throw new Error(`Expected nested value for \"${nestedKey}\" in tools.yaml`);\n          }\n          const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n          objectValue[nestedKey] = nestedValue;\n          index = nextIndex;\n          continue;\n        }\n\n        objectValue[nestedKey] = normalizeScalar(nestedRawValue);\n        index += 1;\n      }\n\n      result.push(objectValue);\n      continue;\n    }\n\n    result.push(normalizeScalar(itemText));\n    index += 1;\n  }\n\n  return [result, index];\n}\n\nfunction parseYamlObject(lines: YamlLine[], startIndex: number, indent: number): [Record<string, unknown>, number] {\n  const result: Record<string, unknown> = {};\n  let index = startIndex;\n\n  while (index < lines.length) {\n    const line = lines[index];\n    if (line.indent < indent) {\n      break;\n    }\n    if (line.indent !== indent || line.text.startsWith('-')) {\n      break;\n    }\n\n    const [key, rawValue] = splitYamlKeyValue(line.text);\n\n    if (rawValue === undefined) {\n      const nextLine = lines[index + 1];\n      if (!nextLine || nextLine.indent <= indent) {\n        result[key] = null;\n        index += 1;\n        continue;\n      }\n\n      const [nestedValue, nextIndex] = parseYamlValue(lines, index + 1, nextLine.indent);\n      result[key] = nestedValue;\n      index = nextIndex;\n      continue;\n    }\n\n    result[key] = normalizeScalar(rawValue);\n    index += 1;\n  }\n\n  return [result, index];\n}\n\nfunction splitYamlKeyValue(input: string): [string, string | undefined] {\n  const separatorIndex = input.indexOf(':');\n  if (separatorIndex === -1) {\n    throw new Error(`Invalid tools.yaml entry: \"${input}\"`);\n  }\n\n  const key = input.slice(0, separatorIndex).trim();\n  const rawValue = input.slice(separatorIndex + 1).trim();\n  return [key, rawValue === '' ? undefined : rawValue];\n}\n\nfunction parseYamlDocument(input: string): unknown {\n  const lines = toYamlLines(input);\n  if (lines.length === 0) {\n    return {};\n  }\n  const [value] = parseYamlValue(lines, 0, lines[0].indent);\n  return value;\n}\n\nfunction ensureExecutionTargetSchema(tool: CustomToolManifestEntry): void {\n  if (tool.inputSchema.properties === undefined) {\n    tool.inputSchema.properties = {};\n  }\n  const properties = tool.inputSchema.properties;\n\n  if (!isPlainObject(properties)) {\n    throw new Error(`Custom tool \"${tool.name}\" inputSchema.properties must be an object`);\n  }\n\n  const pageIdDefinition = properties[REQUIRED_PAGE_ID_FIELD];\n  const browserIdDefinition = properties[OPTIONAL_BROWSER_ID_FIELD];\n\n  if (pageIdDefinition !== undefined && !(isPlainObject(pageIdDefinition) && pageIdDefinition.type === 'string')) {\n    throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.pageId as type \"string\"`);\n  }\n\n  if (\n    browserIdDefinition !== undefined &&\n    !(isPlainObject(browserIdDefinition) && browserIdDefinition.type === 'string')\n  ) {\n    throw new Error(`Custom tool \"${tool.name}\" must define inputSchema.properties.browserId as type \"string\"`);\n  }\n\n  if (pageIdDefinition === undefined) {\n    properties[REQUIRED_PAGE_ID_FIELD] = {\n      type: 'string',\n      description:\n        'Browse-tool page ID to run this custom tool against. Either pageId or browserId is required at call time.',\n    };\n  }\n\n  if (browserIdDefinition === undefined) {\n    properties[OPTIONAL_BROWSER_ID_FIELD] = {\n      type: 'string',\n      description:\n        \"Browse-tool browser ID to run this custom tool against when no pageId is supplied. The tool will use the browser's current page or open a new one.\",\n    };\n  }\n}\n\nasync function loadCustomToolModule(scriptPath: string): Promise<LoadedCustomToolModule> {\n  const source = await readFile(scriptPath, 'utf8');\n  const { code } = await transform(source, { loader: 'ts', format: 'esm', target: 'node20' });\n  const moduleUrl = `data:text/javascript;base64,${Buffer.from(code, 'utf8').toString('base64')}`;\n  return (await import(moduleUrl)) as LoadedCustomToolModule;\n}\n\nfunction validateCustomToolModule(\n  scriptPath: string,\n  loadedModule: LoadedCustomToolModule,\n): {\n  execute?: CustomToolHandler;\n} {\n  const execute = typeof loadedModule.run === 'function' ? (loadedModule.run as CustomToolHandler) : undefined;\n\n  if (!execute) {\n    throw new Error(`Custom tool script \"${scriptPath}\" must export a \"run\" function`);\n  }\n\n  return { execute };\n}\n\nfunction injectSuggestionActionsIntoObject(\n  value: Record<string, unknown>,\n  suggestionActions: string,\n): Record<string, unknown> {\n  if (typeof value.suggestionActions === 'string' && value.suggestionActions.trim().length > 0) {\n    return value;\n  }\n\n  return {\n    ...value,\n    suggestionActions,\n  };\n}\n\nfunction injectSuggestionActionsIntoCallToolResult(result: CallToolResult, suggestionActions: string): CallToolResult {\n  const firstContent = result.content[0];\n  if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n    try {\n      const parsed = JSON.parse(firstContent.text);\n      if (isPlainObject(parsed)) {\n        const merged = injectSuggestionActionsIntoObject(parsed, suggestionActions);\n        return {\n          ...result,\n          content: [{ ...firstContent, text: JSON.stringify(merged, null, 2) }, ...result.content.slice(1)],\n        };\n      }\n    } catch {\n      // Keep the original text payload when it is not valid JSON.\n    }\n  }\n\n  return {\n    ...result,\n    content: [\n      ...result.content,\n      {\n        type: 'text',\n        text: `suggestionActions: ${suggestionActions}`,\n      },\n    ],\n  };\n}\n\nfunction normalizeExecutionResult(toolName: string, value: unknown, suggestionActions?: string): CallToolResult {\n  if (isCallToolResult(value)) {\n    return suggestionActions ? injectSuggestionActionsIntoCallToolResult(value, suggestionActions) : value;\n  }\n\n  if (typeof value === 'string') {\n    if (!suggestionActions) {\n      return { content: [{ type: 'text', text: value }] };\n    }\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify({ result: value, suggestionActions }, null, 2),\n        },\n      ],\n    };\n  }\n\n  if (value === undefined) {\n    if (!suggestionActions) {\n      return { content: [{ type: 'text', text: `Custom tool \"${toolName}\" completed successfully` }] };\n    }\n\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(\n            {\n              result: `Custom tool \"${toolName}\" completed successfully`,\n              suggestionActions,\n            },\n            null,\n            2,\n          ),\n        },\n      ],\n    };\n  }\n\n  const normalizedValue =\n    isPlainObject(value) && suggestionActions ? injectSuggestionActionsIntoObject(value, suggestionActions) : value;\n  return {\n    content: [{ type: 'text', text: JSON.stringify(normalizedValue, null, 2) }],\n  };\n}\n\nexport class CustomToolService {\n  constructor(\n    private readonly pageRegistry: IPageRegistry,\n    private readonly extensionTaskQueue?: ExtensionTaskQueue,\n    private readonly telemetry: ITelemetryService = new TelemetryService(),\n    private readonly browserService?: IBrowserService,\n  ) {}\n\n  private resolveToolPage(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolPage {\n    if (pageEntry.page) {\n      return pageEntry.page;\n    }\n\n    if (pageEntry.mode === 'extension' && this.extensionTaskQueue) {\n      const proxy = new ExtensionPageProxy(this.extensionTaskQueue);\n      proxy.setTarget(pageId, pageEntry.browserId);\n      return proxy;\n    }\n\n    throw new Error(`Custom tool \"${toolName}\" requires a supported page context`);\n  }\n\n  private toPageSummary(currentPageId: string | null, entry: PageEntry): CustomToolBrowserPageSummary {\n    return {\n      pageId: entry.id,\n      url: entry.url,\n      title: entry.title,\n      active: currentPageId === entry.id,\n    };\n  }\n\n  private async createPageForBrowser(\n    toolName: string,\n    browserId: string,\n    options?: { url?: string; setAsCurrent?: boolean },\n  ): Promise<CustomToolBrowserPageHandle> {\n    if (!this.browserService) {\n      throw new Error(`Custom tool \"${toolName}\" requires browser service support to create a page`);\n    }\n\n    const browserInstance = this.browserService.getBrowser(browserId);\n    if (!browserInstance) {\n      throw new Error(`Browser \"${browserId}\" not found`);\n    }\n\n    const setAsCurrent = options?.setAsCurrent !== false;\n\n    if (browserInstance.mode === 'extension' || browserInstance.mode === 'vm') {\n      if (!this.extensionTaskQueue) {\n        throw new Error(`Custom tool \"${toolName}\" requires extension task support to create a page`);\n      }\n\n      const pageId = this.pageRegistry.registerExtensionPage(browserId, undefined, options?.url, false);\n\n      try {\n        const queued = await this.extensionTaskQueue.queueTask(\n          'browser_new_page',\n          {\n            browserId,\n            pageId,\n            url: options?.url,\n            setAsCurrent,\n          },\n          10_000,\n          browserId,\n        );\n\n        // A delivered task can still report failure (e.g. the tab could not be\n        // opened), so an accepted-but-errored result must not register a page.\n        if (!queued.success || queued.result?.isError === true) {\n          throw new Error(queued.error ?? `Custom tool \"${toolName}\" failed to create a page`);\n        }\n\n        const delegatedText = queued.result?.content[0]?.type === 'text' ? queued.result.content[0].text : undefined;\n        let delegatedPayload: { url?: string; title?: string; tabId?: number } = {};\n        if (typeof delegatedText === 'string' && delegatedText.length > 0) {\n          try {\n            delegatedPayload = JSON.parse(delegatedText) as { url?: string; title?: string; tabId?: number };\n          } catch {\n            delegatedPayload = {};\n          }\n        }\n\n        const pageEntry = this.pageRegistry.get(pageId);\n        if (!pageEntry) {\n          throw new Error(`Page \"${pageId}\" was not registered`);\n        }\n\n        pageEntry.url = delegatedPayload.url ?? pageEntry.url;\n        pageEntry.title = delegatedPayload.title ?? pageEntry.title;\n        pageEntry.extensionTabId = delegatedPayload.tabId ?? pageEntry.extensionTabId;\n        const resolvedPageEntry = await this.waitForResolvedPageMetadata(pageId);\n        browserInstance.pageIds.add(pageId);\n        if (setAsCurrent || !browserInstance.currentPageId) {\n          this.browserService.setCurrentPage(browserId, pageId);\n        }\n        this.browserService.recordBrowserActivity(browserId, pageId);\n\n        return {\n          ...this.toPageSummary(browserInstance.currentPageId, resolvedPageEntry),\n          page: this.resolveToolPage(toolName, pageId, resolvedPageEntry),\n        };\n      } catch (error) {\n        this.pageRegistry.remove(pageId);\n        throw error;\n      }\n    }\n\n    const { pageId, page } = await this.browserService.newPage(browserId);\n    if (options?.url) {\n      await page.goto(options.url);\n      await this.pageRegistry.updateMetadata(pageId);\n    }\n\n    if (setAsCurrent) {\n      this.browserService.setCurrentPage(browserId, pageId);\n    }\n    this.browserService.recordBrowserActivity(browserId, pageId);\n\n    const pageEntry = this.pageRegistry.get(pageId);\n    if (!pageEntry) {\n      throw new Error(`Page \"${pageId}\" was not registered`);\n    }\n\n    return {\n      ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n      page: this.resolveToolPage(toolName, pageId, pageEntry),\n    };\n  }\n\n  private async waitForResolvedPageMetadata(pageId: string): Promise<PageEntry> {\n    const startedAt = Date.now();\n    let entry = this.pageRegistry.get(pageId);\n\n    while (entry && Date.now() - startedAt < CUSTOM_TOOL_PAGE_METADATA_WAIT_TIMEOUT_MS) {\n      const hasUrl = typeof entry.url === 'string' && entry.url.length > 0;\n      const hasTitle = typeof entry.title === 'string' && entry.title.length > 0 && entry.title !== 'Extension Tab';\n      if (hasUrl && hasTitle) {\n        return entry;\n      }\n\n      await new Promise((resolve) => setTimeout(resolve, CUSTOM_TOOL_PAGE_METADATA_WAIT_POLL_MS));\n      entry = this.pageRegistry.get(pageId);\n    }\n\n    if (!entry) {\n      throw new Error(`Page \"${pageId}\" was not registered`);\n    }\n\n    return entry;\n  }\n\n  private async resolveExecutionContext(\n    toolName: string,\n    input: Record<string, unknown>,\n  ): Promise<{ pageId: string; pageEntry: PageEntry; page: CustomToolPage; browser: CustomToolBrowser }> {\n    const requestedPageId =\n      typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' && input[REQUIRED_PAGE_ID_FIELD].length > 0\n        ? input[REQUIRED_PAGE_ID_FIELD]\n        : undefined;\n    const requestedBrowserId =\n      typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' && input[OPTIONAL_BROWSER_ID_FIELD].length > 0\n        ? input[OPTIONAL_BROWSER_ID_FIELD]\n        : undefined;\n\n    if (!requestedPageId && !requestedBrowserId) {\n      throw new Error(`Custom tool \"${toolName}\" requires a string pageId or browserId`);\n    }\n\n    if (requestedPageId) {\n      const pageEntry = this.pageRegistry.get(requestedPageId);\n      if (!pageEntry) {\n        throw new Error(`Page \"${requestedPageId}\" not found`);\n      }\n      assertPageOwnership(pageEntry, `Custom tool \"${toolName}\"`);\n      if (requestedBrowserId && pageEntry.browserId !== requestedBrowserId) {\n        throw new Error(\n          `Custom tool \"${toolName}\" received pageId \"${requestedPageId}\" for browser \"${pageEntry.browserId}\", not \"${requestedBrowserId}\"`,\n        );\n      }\n\n      const browser = this.createBrowserHelper(toolName, pageEntry.browserId);\n      return {\n        pageId: requestedPageId,\n        pageEntry,\n        page: this.resolveToolPage(toolName, requestedPageId, pageEntry),\n        browser,\n      };\n    }\n\n    const browser = this.createBrowserHelper(toolName, requestedBrowserId as string);\n    try {\n      const pageHandle = await browser.getCurrentPage();\n      const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n      if (!pageEntry) {\n        throw new Error(`Page \"${pageHandle.pageId}\" not found`);\n      }\n\n      return {\n        pageId: pageHandle.pageId,\n        pageEntry,\n        page: pageHandle.page,\n        browser,\n      };\n    } catch (error) {\n      const message = error instanceof Error ? error.message : String(error);\n      if (!message.includes('has no pages')) {\n        throw error;\n      }\n\n      const pageHandle = await browser.newPage();\n      const pageEntry = this.pageRegistry.get(pageHandle.pageId);\n      if (!pageEntry) {\n        throw new Error(`Page \"${pageHandle.pageId}\" not found`, {\n          cause: error,\n        });\n      }\n\n      return {\n        pageId: pageHandle.pageId,\n        pageEntry,\n        page: pageHandle.page,\n        browser,\n      };\n    }\n  }\n\n  private createBrowserHelper(toolName: string, browserId: string): CustomToolBrowser {\n    const listPages = async (): Promise<CustomToolBrowserPageSummary[]> => {\n      const browserInstance = this.browserService?.getBrowser(browserId);\n      if (!browserInstance) {\n        throw new Error(`Browser \"${browserId}\" not found`);\n      }\n\n      return this.pageRegistry\n        .findByBrowser(browserId)\n        .map((entry) => this.toPageSummary(browserInstance.currentPageId, entry));\n    };\n\n    return {\n      browserId,\n      mode: this.browserService?.getBrowser(browserId)?.mode ?? 'extension',\n      listPages,\n      getPage: async (pageId: string): Promise<CustomToolBrowserPageHandle> => {\n        const pageEntry = this.pageRegistry.get(pageId);\n        if (!pageEntry || pageEntry.browserId !== browserId) {\n          throw new Error(`Page \"${pageId}\" not found in browser \"${browserId}\"`);\n        }\n\n        const browserInstance = this.browserService?.getBrowser(browserId);\n        return {\n          ...this.toPageSummary(browserInstance?.currentPageId ?? null, pageEntry),\n          page: this.resolveToolPage(toolName, pageId, pageEntry),\n        };\n      },\n      getCurrentPage: async (): Promise<CustomToolBrowserPageHandle> => {\n        const browserInstance = this.browserService?.getBrowser(browserId);\n        if (!browserInstance) {\n          throw new Error(`Browser \"${browserId}\" not found`);\n        }\n\n        const ownerId = getCurrentOwnerId();\n        let currentPageId: string | undefined;\n        if (ownerId) {\n          // Scope \"current page\" to the calling session so concurrent agents sharing\n          // this profile browser never resolve each other's tab.\n          const ownerPages = this.pageRegistry.findByOwner(ownerId).filter((entry) => entry.browserId === browserId);\n          currentPageId = ownerPages[ownerPages.length - 1]?.id;\n          if (!currentPageId) {\n            throw new Error(`Browser \"${browserId}\" has no pages owned by this session`);\n          }\n        } else {\n          currentPageId =\n            browserInstance.currentPageId ?? this.pageRegistry.findByBrowser(browserId)[0]?.id ?? undefined;\n        }\n        if (!currentPageId) {\n          throw new Error(`Browser \"${browserId}\" has no pages`);\n        }\n\n        const pageEntry = this.pageRegistry.get(currentPageId);\n        if (!pageEntry) {\n          throw new Error(`Page \"${currentPageId}\" not found`);\n        }\n\n        return {\n          ...this.toPageSummary(browserInstance.currentPageId, pageEntry),\n          page: this.resolveToolPage(toolName, currentPageId, pageEntry),\n        };\n      },\n      newPage: async (options?: { url?: string; setAsCurrent?: boolean }): Promise<CustomToolBrowserPageHandle> =>\n        this.createPageForBrowser(toolName, browserId, options),\n    };\n  }\n\n  private createToolLogger(toolName: string, pageId: string, pageEntry: PageEntry): CustomToolLogger {\n    const baseAttributes: Record<string, unknown> = {\n      'browse_tool.tool.name': toolName,\n      'browse_tool.page.id': pageId,\n      'browse_tool.browser.id': pageEntry.browserId,\n      'browse_tool.execution.mode': pageEntry.mode,\n    };\n\n    const emit = (\n      level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal',\n      message: string,\n      options?: CustomToolLogOptions,\n    ): void => {\n      this.telemetry.log(level, message, {\n        attributes: toAttributes({\n          ...baseAttributes,\n          ...options?.attributes,\n        }),\n        exception: options?.exception,\n      });\n    };\n\n    return {\n      getTraceContext: () => this.telemetry.getActiveTraceContext(),\n      trace: (message, options) => emit('trace', message, options),\n      debug: (message, options) => emit('debug', message, options),\n      info: (message, options) => emit('info', message, options),\n      warn: (message, options) => emit('warn', message, options),\n      error: (message, options) => emit('error', message, options),\n      fatal: (message, options) => emit('fatal', message, options),\n    };\n  }\n\n  async listTools(directory: string): Promise<CustomToolDefinition[]> {\n    const tools = await this.loadTools(directory);\n    return tools.map(({ name, description, suggestionActions, inputSchema, capabilities }) => ({\n      name,\n      description,\n      suggestionActions,\n      inputSchema,\n      capabilities,\n    }));\n  }\n\n  async executeTool(directory: string, toolName: string, input: Record<string, unknown>): Promise<CallToolResult> {\n    const requestedPageId =\n      typeof input[REQUIRED_PAGE_ID_FIELD] === 'string' ? input[REQUIRED_PAGE_ID_FIELD] : undefined;\n    const requestedBrowserId =\n      typeof input[OPTIONAL_BROWSER_ID_FIELD] === 'string' ? input[OPTIONAL_BROWSER_ID_FIELD] : undefined;\n\n    return this.telemetry.runInSpan(\n      'browse_tool.custom_tool.execute',\n      {\n        attributes: {\n          'browse_tool.tool.name': toolName,\n          'browse_tool.custom_tools.directory': path.resolve(directory),\n          'browse_tool.page.id': requestedPageId,\n          'browse_tool.browser.id': requestedBrowserId,\n        },\n      },\n      async (span) => {\n        const tools = await this.loadTools(directory);\n        const tool = tools.find((candidate) => candidate.name === toolName);\n\n        if (!tool) {\n          span?.setStatus({ code: SpanStatusCode.ERROR, message: `Custom tool \"${toolName}\" not found` });\n          throw new Error(`Custom tool \"${toolName}\" not found`);\n        }\n\n        const { pageId, pageEntry, page, browser } = await this.resolveExecutionContext(toolName, input);\n        const logger = this.createToolLogger(toolName, pageId, pageEntry);\n        span?.setAttributes({\n          'browse_tool.browser.id': pageEntry.browserId,\n          'browse_tool.execution.mode': pageEntry.mode,\n          'browse_tool.page.id': pageId,\n        });\n        debugCustomTool('Executing custom tool', {\n          toolName,\n          directory: path.resolve(directory),\n          pageId,\n          browserId: pageEntry.browserId,\n          mode: pageEntry.mode,\n          input: summarizeValue(input),\n          scriptPath: tool.scriptPath,\n        });\n\n        let rawResult;\n        try {\n          rawResult = await tool.execute?.({ page, browser, input, logger });\n        } catch (error) {\n          const message = error instanceof Error ? error.message : String(error);\n          debugCustomTool('Custom tool execution failed', {\n            toolName,\n            pageId,\n            browserId: pageEntry.browserId,\n            mode: pageEntry.mode,\n            error: message,\n            stack: error instanceof Error ? error.stack : undefined,\n          });\n          throw new Error(`Custom tool \"${toolName}\" failed on page \"${pageId}\": ${message}`, {\n            cause: error,\n          });\n        }\n\n        debugCustomTool('Custom tool execution completed', {\n          toolName,\n          pageId,\n          browserId: pageEntry.browserId,\n          mode: pageEntry.mode,\n          result: summarizeValue(rawResult),\n        });\n\n        const result = normalizeExecutionResult(toolName, rawResult, tool.suggestionActions);\n        const errorMessage = result.isError ? (result.content[0] as { text?: string })?.text : undefined;\n        if (errorMessage) {\n          span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n        }\n\n        return result;\n      },\n    );\n  }\n\n  private async loadTools(directory: string): Promise<LoadedCustomTool[]> {\n    const resolvedDirectory = path.resolve(directory);\n    const manifestPath = path.join(resolvedDirectory, MANIFEST_FILE);\n    const manifestSource = await readFile(manifestPath, 'utf8').catch((error) => {\n      throw new Error(\n        `Failed to read custom tool manifest at \"${manifestPath}\": ${error instanceof Error ? error.message : String(error)}`,\n      );\n    });\n\n    const parsedManifest = parseYamlDocument(manifestSource);\n    const manifest = CustomToolManifestSchema.parse(parsedManifest);\n\n    return Promise.all(\n      manifest.tools.map(async (tool) => {\n        ensureExecutionTargetSchema(tool);\n\n        const scriptPath = path.resolve(resolvedDirectory, tool.script);\n        await stat(scriptPath).catch((error) => {\n          throw new Error(\n            `Custom tool \"${tool.name}\" script not found at \"${scriptPath}\": ${error instanceof Error ? error.message : String(error)}`,\n          );\n        });\n\n        const loadedModule = await loadCustomToolModule(scriptPath);\n        const executionHooks = validateCustomToolModule(scriptPath, loadedModule);\n\n        return {\n          name: tool.name,\n          description: tool.description,\n          suggestionActions: tool.suggestionActions,\n          inputSchema: tool.inputSchema,\n          capabilities: tool.capabilities,\n          scriptPath,\n          ...executionHooks,\n        };\n      }),\n    );\n  }\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\n\n/**\n * Browser data for API response\n */\ninterface BrowserResponse {\n  id: string;\n  profileName?: string;\n  currentPageId: string | null;\n  createdAt: string;\n  pages: PageResponse[];\n}\n\n/**\n * Page data for API response\n */\ninterface PageResponse {\n  id: string;\n  url: string;\n  title: string;\n  createdAt: string;\n}\n\n/**\n * Stats for API response\n */\ninterface StatsResponse {\n  totalBrowsers: number;\n  totalPages: number;\n}\n\n/**\n * Create API router for dashboard\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with API endpoints\n */\nexport function createApiRouter(container: Container): Hono {\n  const app = new Hono();\n\n  /**\n   * GET /api/browsers - List all browsers with nested pages and stats\n   */\n  app.get('/browsers', async (c) => {\n    try {\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n      const browserInstances = browserService.listBrowsers();\n      const allPages = pageRegistry.list();\n\n      // Build browser response with nested pages\n      const browsers: BrowserResponse[] = browserInstances.map((browser: BrowserInstance) => {\n        const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n        return {\n          id: browser.id,\n          profileName: browser.profileName,\n          currentPageId: browser.currentPageId,\n          createdAt: browser.createdAt.toISOString(),\n          pages: browserPages.map((page: PageEntry) => ({\n            id: page.id,\n            url: page.url,\n            title: page.title,\n            createdAt: page.createdAt.toISOString(),\n          })),\n        };\n      });\n\n      const stats: StatsResponse = {\n        totalBrowsers: browserInstances.length,\n        totalPages: allPages.length,\n      };\n\n      return c.json({ browsers, stats });\n    } catch (error) {\n      console.error('Failed to list browsers:', error);\n      return c.json(\n        {\n          error: 'Failed to list browsers',\n          message: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * DELETE /api/browsers - Close all browsers\n   */\n  app.delete('/browsers', async (c) => {\n    try {\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      await browserService.closeAll();\n\n      return c.json({ success: true, message: 'All browsers closed' });\n    } catch (error) {\n      console.error('Failed to close all browsers:', error);\n      return c.json(\n        {\n          error: 'Failed to close all browsers',\n          message: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * DELETE /api/browsers/:id - Close a specific browser\n   */\n  app.delete('/browsers/:id', async (c) => {\n    try {\n      const browserId = c.req.param('id');\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n\n      const browser = browserService.getBrowser(browserId);\n      if (!browser) {\n        return c.json({ error: `Browser \"${browserId}\" not found` }, 404);\n      }\n\n      await browserService.closeBrowser(browserId);\n\n      return c.json({ success: true, message: `Browser \"${browserId}\" closed` });\n    } catch (error) {\n      console.error('Failed to close browser:', error);\n      return c.json(\n        {\n          error: 'Failed to close browser',\n          message: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  /**\n   * DELETE /api/pages/:id - Close a specific page\n   */\n  app.delete('/pages/:id', async (c) => {\n    try {\n      const pageId = c.req.param('id');\n      const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n      const pageEntry = pageRegistry.get(pageId);\n      if (!pageEntry) {\n        return c.json({ error: `Page \"${pageId}\" not found` }, 404);\n      }\n\n      if (!pageEntry.page) {\n        return c.json({ error: `Page \"${pageId}\" is in extension mode and cannot be closed via API` }, 400);\n      }\n\n      // Close the page - this will trigger the registry cleanup via the 'close' event\n      await pageEntry.page.close();\n\n      return c.json({ success: true, message: `Page \"${pageId}\" closed` });\n    } catch (error) {\n      console.error('Failed to close page:', error);\n      return c.json(\n        {\n          error: 'Failed to close page',\n          message: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  return app;\n}\n","/**\n * Utility formatters for dashboard display\n */\n\n/**\n * Format a Date object to a human-readable string\n *\n * @param date - Date to format\n * @returns Formatted timestamp string\n */\nexport function formatTimestamp(date: Date): string {\n  return date.toLocaleString('en-US', {\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n    second: '2-digit',\n    hour12: false,\n  });\n}\n\n/**\n * Format duration in milliseconds to human readable\n *\n * @param ms - Duration in milliseconds\n * @returns Formatted duration string\n */\nexport function formatDuration(ms: number): string {\n  const seconds = Math.floor(ms / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const hours = Math.floor(minutes / 60);\n\n  if (hours > 0) {\n    return `${hours}h ${minutes % 60}m`;\n  }\n  if (minutes > 0) {\n    return `${minutes}m ${seconds % 60}s`;\n  }\n  return `${seconds}s`;\n}\n\n/**\n * Calculate duration since a given date\n *\n * @param date - Start date\n * @returns Duration string\n */\nexport function formatAge(date: Date): string {\n  const now = new Date();\n  const ms = now.getTime() - date.getTime();\n  return formatDuration(ms);\n}\n","// Plain CSS class names for SSR compatibility\nexport const container = 'dashboard-container';\nexport const header = 'dashboard-header';\nexport const controlsSection = 'controls-section';\nexport const button = 'btn';\nexport const buttonDanger = 'btn-danger';\nexport const tableContainer = 'table-container';\nexport const table = 'browser-table';\nexport const statsContainer = 'stats-container';\nexport const statCard = 'stat-card';\nexport const browserRow = 'browser-row';\nexport const pageRow = 'page-row';\nexport const statusActive = 'status-active';\nexport const statusInactive = 'status-inactive';\nexport const urlCell = 'url-cell';\nexport const timestampCell = 'timestamp-cell';\nexport const actionsCell = 'actions-cell';\nexport const killButton = 'kill-btn';\nexport const refreshButton = 'refresh-btn';\nexport const emptyState = 'empty-state';\n\n// Global styles as plain CSS string for SSR\nexport const globalStyles = `\n  * {\n    margin: 0;\n    padding: 0;\n    box-sizing: border-box;\n  }\n\n  body {\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n    font-size: 14px;\n    line-height: 1.5;\n    color: #333;\n    background-color: #f5f5f5;\n  }\n\n  h1, h2, h3 {\n    margin-bottom: 1rem;\n  }\n\n  .dashboard-container {\n    max-width: 1400px;\n    margin: 0 auto;\n    padding: 2rem;\n  }\n\n  .dashboard-header {\n    background-color: #fff;\n    padding: 1.5rem;\n    margin-bottom: 1.5rem;\n    border-radius: 8px;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .dashboard-header h1 {\n    margin-bottom: 0;\n  }\n\n  .controls-section {\n    background-color: #fff;\n    padding: 1rem;\n    margin-bottom: 1rem;\n    border-radius: 8px;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    display: flex;\n    gap: 1rem;\n    align-items: center;\n    flex-wrap: wrap;\n  }\n\n  .btn {\n    padding: 0.5rem 1rem;\n    border: none;\n    border-radius: 4px;\n    font-size: 14px;\n    cursor: pointer;\n    background-color: #2196f3;\n    color: white;\n    transition: background-color 0.2s;\n  }\n\n  .btn:hover {\n    background-color: #1976d2;\n  }\n\n  .btn:disabled {\n    background-color: #ccc;\n    cursor: not-allowed;\n  }\n\n  .btn-danger {\n    background-color: #f44336;\n  }\n\n  .btn-danger:hover {\n    background-color: #d32f2f;\n  }\n\n  .table-container {\n    background-color: #fff;\n    border-radius: 8px;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    overflow: hidden;\n  }\n\n  .browser-table {\n    width: 100%;\n    border-collapse: collapse;\n    table-layout: fixed;\n  }\n\n  .browser-table th {\n    background-color: #f8f9fa;\n    padding: 0.75rem;\n    text-align: left;\n    font-weight: 600;\n    border-bottom: 2px solid #dee2e6;\n    position: sticky;\n    top: 0;\n  }\n\n  .browser-table th:nth-child(1) {\n    width: 12%;\n  }\n\n  .browser-table th:nth-child(2) {\n    width: 10%;\n  }\n\n  .browser-table th:nth-child(3) {\n    width: 38%;\n  }\n\n  .browser-table th:nth-child(4) {\n    width: 15%;\n  }\n\n  .browser-table th:nth-child(5) {\n    width: 15%;\n  }\n\n  .browser-table th:nth-child(6) {\n    width: 10%;\n  }\n\n  .browser-table td {\n    padding: 0.75rem;\n    border-bottom: 1px solid #dee2e6;\n    vertical-align: middle;\n  }\n\n  .browser-row {\n    background-color: #e3f2fd;\n    font-weight: 500;\n  }\n\n  .browser-row:hover {\n    background-color: #bbdefb;\n  }\n\n  .page-row {\n    background-color: #fff;\n  }\n\n  .page-row:hover {\n    background-color: #f8f9fa;\n  }\n\n  .page-row td:first-child {\n    padding-left: 2rem;\n  }\n\n  .status-active {\n    color: #4caf50;\n    font-weight: 600;\n  }\n\n  .status-inactive {\n    color: #9e9e9e;\n  }\n\n  .url-cell {\n    max-width: 400px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    font-family: 'Courier New', monospace;\n    font-size: 13px;\n  }\n\n  .timestamp-cell {\n    white-space: nowrap;\n    font-size: 12px;\n    color: #666;\n  }\n\n  .actions-cell {\n    text-align: center;\n  }\n\n  .kill-btn {\n    padding: 0.25rem 0.5rem;\n    font-size: 12px;\n    background-color: #ff5722;\n    border: none;\n    border-radius: 4px;\n    color: white;\n    cursor: pointer;\n    transition: background-color 0.2s;\n  }\n\n  .kill-btn:hover {\n    background-color: #e64a19;\n  }\n\n  .refresh-btn {\n    background-color: #4caf50;\n  }\n\n  .refresh-btn:hover {\n    background-color: #388e3c;\n  }\n\n  .stats-container {\n    display: flex;\n    gap: 1.5rem;\n    margin-bottom: 1rem;\n  }\n\n  .stat-card {\n    background-color: #fff;\n    padding: 1rem;\n    border-radius: 8px;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n    flex: 1;\n  }\n\n  .stat-card h3 {\n    font-size: 0.875rem;\n    color: #666;\n    margin-bottom: 0.5rem;\n  }\n\n  .stat-card .value {\n    font-size: 1.5rem;\n    font-weight: 700;\n    color: #333;\n  }\n\n  .empty-state {\n    padding: 3rem;\n    text-align: center;\n    color: #666;\n  }\n\n  .empty-state h3 {\n    margin-bottom: 0.5rem;\n    color: #999;\n  }\n`;\n","import { Fragment } from 'hono/jsx';\nimport { formatAge, formatTimestamp } from '../utils/formatters.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n  id: string;\n  url: string;\n  title: string;\n  createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n  id: string;\n  profileName?: string;\n  currentPageId: string | null;\n  createdAt: Date;\n  pages: PageData[];\n}\n\n/**\n * BrowserTable component props\n */\ninterface BrowserTableProps {\n  browsers: BrowserData[];\n}\n\n/**\n * BrowserTable component\n *\n * Displays browsers and their pages in a hierarchical table\n */\nexport function BrowserTable({ browsers }: BrowserTableProps) {\n  if (browsers.length === 0) {\n    return (\n      <div class={styles.tableContainer}>\n        <div class={styles.emptyState}>\n          <h3>No Active Browsers</h3>\n          <p>Launch a browser using MCP tools to see it here.</p>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div class={styles.tableContainer}>\n      <table class={styles.table}>\n        <thead>\n          <tr>\n            <th>ID</th>\n            <th>Status</th>\n            <th>URL / Title</th>\n            <th>Created</th>\n            <th>Age</th>\n            <th>Actions</th>\n          </tr>\n        </thead>\n        <tbody id=\"browser-table-body\">\n          {browsers.map((browser) => (\n            <Fragment key={browser.id}>\n              <tr class={styles.browserRow}>\n                <td>{browser.id}</td>\n                <td>\n                  <span class={styles.statusActive}>\n                    {browser.pages.length} page{browser.pages.length !== 1 ? 's' : ''}\n                  </span>\n                </td>\n                <td>{browser.profileName || 'Default Profile'}</td>\n                <td class={styles.timestampCell}>{formatTimestamp(browser.createdAt)}</td>\n                <td class={styles.timestampCell}>{formatAge(browser.createdAt)}</td>\n                <td class={styles.actionsCell}>\n                  {/* Ids arrive from connecting extensions, so they are carried\n                      as data rather than spliced into an onclick script. */}\n                  <button type=\"button\" class={styles.killButton} data-browser-id={browser.id}>\n                    Kill\n                  </button>\n                </td>\n              </tr>\n              {browser.pages.map((page) => (\n                <tr key={page.id} class={styles.pageRow}>\n                  <td>\n                    {page.id}\n                    {browser.currentPageId === page.id && ' (active)'}\n                  </td>\n                  <td>\n                    <span class={styles.statusActive}>Open</span>\n                  </td>\n                  <td class={styles.urlCell} title={page.url}>\n                    {page.title || page.url || 'about:blank'}\n                  </td>\n                  <td class={styles.timestampCell}>{formatTimestamp(page.createdAt)}</td>\n                  <td class={styles.timestampCell}>{formatAge(page.createdAt)}</td>\n                  <td class={styles.actionsCell}>\n                    <button type=\"button\" class={styles.killButton} data-page-id={page.id}>\n                      Close\n                    </button>\n                  </td>\n                </tr>\n              ))}\n            </Fragment>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  );\n}\n","import * as styles from './styles.js';\n\n/**\n * Stats data structure\n */\ninterface Stats {\n  totalBrowsers: number;\n  totalPages: number;\n}\n\n/**\n * StatsHeader component props\n */\ninterface StatsHeaderProps {\n  stats: Stats;\n}\n\n/**\n * StatsHeader component\n *\n * Displays browser and page statistics\n */\nexport function StatsHeader({ stats }: StatsHeaderProps) {\n  return (\n    <div class={styles.statsContainer}>\n      <div class={styles.statCard}>\n        <h3>Active Browsers</h3>\n        <div class=\"value\">{stats.totalBrowsers}</div>\n      </div>\n      <div class={styles.statCard}>\n        <h3>Active Pages</h3>\n        <div class=\"value\">{stats.totalPages}</div>\n      </div>\n    </div>\n  );\n}\n","import { raw } from 'hono/html';\nimport { BrowserTable } from './BrowserTable.js';\nimport { StatsHeader } from './StatsHeader.js';\nimport * as styles from './styles.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n  id: string;\n  url: string;\n  title: string;\n  createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n  id: string;\n  profileName?: string;\n  currentPageId: string | null;\n  createdAt: Date;\n  pages: PageData[];\n}\n\n/**\n * Stats data structure\n */\ninterface Stats {\n  totalBrowsers: number;\n  totalPages: number;\n}\n\n/**\n * Dashboard component props\n */\ninterface DashboardProps {\n  browsers: BrowserData[];\n  stats: Stats;\n}\n\n/**\n * Dashboard component\n *\n * Main dashboard UI with auto-refresh polling and browser/page management\n */\nexport function Dashboard({ browsers, stats }: DashboardProps) {\n  return (\n    <div class={styles.container}>\n      <div class={styles.header}>\n        <div>\n          <h1>Playwright MCP Dashboard</h1>\n          <p>Browser automation management (auto-refresh every 3 seconds)</p>\n        </div>\n        <div>\n          <button\n            type=\"button\"\n            id=\"refresh-btn\"\n            class={`${styles.button} ${styles.refreshButton}`}\n            onclick=\"dashboard.manualRefresh()\"\n          >\n            Refresh Now\n          </button>\n          <button\n            type=\"button\"\n            id=\"kill-all-btn\"\n            class={`${styles.button} ${styles.buttonDanger}`}\n            onclick=\"dashboard.killAllBrowsers()\"\n          >\n            Kill All Browsers\n          </button>\n        </div>\n      </div>\n\n      <StatsHeader stats={stats} />\n\n      <BrowserTable browsers={browsers} />\n\n      <script>\n        {raw(`\nclass DashboardManager {\n  constructor() {\n    this.autoRefreshInterval = null;\n    this.isRefreshing = false;\n    this.startAutoRefresh();\n  }\n\n  startAutoRefresh() {\n    this.autoRefreshInterval = setInterval(() => {\n      this.fetchBrowsers();\n    }, 3000);\n  }\n\n  stopAutoRefresh() {\n    if (this.autoRefreshInterval) {\n      clearInterval(this.autoRefreshInterval);\n      this.autoRefreshInterval = null;\n    }\n  }\n\n  async fetchBrowsers() {\n    if (this.isRefreshing) return;\n    this.isRefreshing = true;\n\n    try {\n      const response = await fetch('/api/browsers');\n      const data = await response.json();\n      this.updateStats(data.stats);\n      this.updateBrowserTable(data.browsers);\n    } catch (error) {\n      console.error('Failed to fetch browsers:', error);\n    } finally {\n      this.isRefreshing = false;\n    }\n  }\n\n  updateStats(stats) {\n    const cards = document.querySelectorAll('.${styles.statCard} .value');\n    if (cards.length >= 2) {\n      cards[0].textContent = stats.totalBrowsers;\n      cards[1].textContent = stats.totalPages;\n    }\n  }\n\n  updateBrowserTable(browsers) {\n    const tbody = document.getElementById('browser-table-body');\n    if (!tbody) return;\n\n    if (browsers.length === 0) {\n      const container = tbody.closest('.${styles.tableContainer}');\n      if (container) {\n        container.innerHTML = \\`\n          <div class=\"${styles.emptyState}\">\n            <h3>No Active Browsers</h3>\n            <p>Launch a browser using MCP tools to see it here.</p>\n          </div>\n        \\`;\n      }\n      return;\n    }\n\n    // Rebuilt with DOM APIs, never innerHTML: browser and page ids come from\n    // whatever a connecting extension claimed, so treating them as markup would\n    // let a crafted id run script in this page's origin. textContent and\n    // dataset never reinterpret their input.\n    tbody.innerHTML = '';\n    browsers.forEach(browser => {\n      const browserRow = document.createElement('tr');\n      browserRow.className = '${styles.browserRow}';\n      this.appendTextCell(browserRow, browser.id);\n      this.appendBadgeCell(browserRow, browser.pages.length + ' page' + (browser.pages.length !== 1 ? 's' : ''));\n      this.appendTextCell(browserRow, browser.profileName || 'Default Profile');\n      this.appendTextCell(browserRow, this.formatTimestamp(browser.createdAt), '${styles.timestampCell}');\n      this.appendTextCell(browserRow, this.formatAge(browser.createdAt), '${styles.timestampCell}');\n      this.appendActionCell(browserRow, 'Kill', 'browserId', browser.id);\n      tbody.appendChild(browserRow);\n\n      browser.pages.forEach(page => {\n        const pageRow = document.createElement('tr');\n        pageRow.className = '${styles.pageRow}';\n        const isActive = browser.currentPageId === page.id;\n        this.appendTextCell(pageRow, page.id + (isActive ? ' (active)' : ''));\n        this.appendBadgeCell(pageRow, 'Open');\n        const urlCell = this.appendTextCell(\n          pageRow,\n          page.title || page.url || 'about:blank',\n          '${styles.urlCell}',\n        );\n        urlCell.title = page.url || '';\n        this.appendTextCell(pageRow, this.formatTimestamp(page.createdAt), '${styles.timestampCell}');\n        this.appendTextCell(pageRow, this.formatAge(page.createdAt), '${styles.timestampCell}');\n        this.appendActionCell(pageRow, 'Close', 'pageId', page.id);\n        tbody.appendChild(pageRow);\n      });\n    });\n  }\n\n  appendTextCell(row, text, className) {\n    const cell = document.createElement('td');\n    if (className) cell.className = className;\n    cell.textContent = text;\n    row.appendChild(cell);\n    return cell;\n  }\n\n  appendBadgeCell(row, text) {\n    const cell = document.createElement('td');\n    const badge = document.createElement('span');\n    badge.className = '${styles.statusActive}';\n    badge.textContent = text;\n    cell.appendChild(badge);\n    row.appendChild(cell);\n    return cell;\n  }\n\n  appendActionCell(row, label, datasetKey, id) {\n    const cell = document.createElement('td');\n    cell.className = '${styles.actionsCell}';\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.className = '${styles.killButton}';\n    button.textContent = label;\n    // Carried as data, not as generated JS in an onclick attribute: the HTML\n    // parser decodes entities before the JS parser runs, so escaping alone\n    // cannot make an id safe to embed in a script.\n    button.dataset[datasetKey] = id;\n    cell.appendChild(button);\n    row.appendChild(cell);\n    return cell;\n  }\n\n  bindTableActions() {\n    const tbody = document.getElementById('browser-table-body');\n    if (!tbody) return;\n\n    tbody.addEventListener('click', event => {\n      const button = event.target.closest('button[data-browser-id], button[data-page-id]');\n      if (!button) return;\n\n      if (button.dataset.browserId) {\n        this.killBrowser(button.dataset.browserId);\n      } else if (button.dataset.pageId) {\n        this.killPage(button.dataset.pageId);\n      }\n    });\n  }\n\n  formatTimestamp(timestamp) {\n    const date = new Date(timestamp);\n    return date.toLocaleString('en-US', {\n      year: 'numeric',\n      month: '2-digit',\n      day: '2-digit',\n      hour: '2-digit',\n      minute: '2-digit',\n      second: '2-digit',\n      hour12: false,\n    });\n  }\n\n  formatAge(timestamp) {\n    const now = new Date();\n    const date = new Date(timestamp);\n    const ms = now.getTime() - date.getTime();\n    const seconds = Math.floor(ms / 1000);\n    const minutes = Math.floor(seconds / 60);\n    const hours = Math.floor(minutes / 60);\n\n    if (hours > 0) return \\`\\${hours}h \\${minutes % 60}m\\`;\n    if (minutes > 0) return \\`\\${minutes}m \\${seconds % 60}s\\`;\n    return \\`\\${seconds}s\\`;\n  }\n\n  escapeHtml(str) {\n    if (typeof str !== 'string') return str;\n    return str\n      .replace(/&/g, '&amp;')\n      .replace(/</g, '&lt;')\n      .replace(/>/g, '&gt;')\n      .replace(/\"/g, '&quot;')\n      .replace(/'/g, '&#039;');\n  }\n\n  manualRefresh() {\n    this.fetchBrowsers();\n  }\n\n  async killBrowser(browserId) {\n    try {\n      const response = await fetch(\\`/api/browsers/\\${browserId}\\`, { method: 'DELETE' });\n      if (response.ok) {\n        this.fetchBrowsers();\n      } else {\n        console.error('Failed to kill browser:', await response.text());\n      }\n    } catch (error) {\n      console.error('Failed to kill browser:', error);\n    }\n  }\n\n  async killPage(pageId) {\n    try {\n      const response = await fetch(\\`/api/pages/\\${pageId}\\`, { method: 'DELETE' });\n      if (response.ok) {\n        this.fetchBrowsers();\n      } else {\n        console.error('Failed to close page:', await response.text());\n      }\n    } catch (error) {\n      console.error('Failed to close page:', error);\n    }\n  }\n\n  async killAllBrowsers() {\n    if (!confirm('Are you sure you want to close all browsers?')) return;\n\n    try {\n      const response = await fetch('/api/browsers', { method: 'DELETE' });\n      if (response.ok) {\n        this.fetchBrowsers();\n      } else {\n        console.error('Failed to kill all browsers:', await response.text());\n      }\n    } catch (error) {\n      console.error('Failed to kill all browsers:', error);\n    }\n  }\n}\n\n// Initialize dashboard\nconst dashboard = new DashboardManager();\ndashboard.bindTableActions();\n\n// Cleanup on page unload\nwindow.addEventListener('beforeunload', () => {\n  dashboard.stopAutoRefresh();\n});\n        `)}\n      </script>\n    </div>\n  );\n}\n","import { raw } from 'hono/html';\nimport { globalStyles } from './styles.js';\n\n/**\n * Layout component props\n */\ninterface LayoutProps {\n  title: string;\n  children: unknown;\n}\n\n/**\n * Layout component\n *\n * Provides the HTML wrapper with styles and meta tags for SSR\n */\nexport function Layout({ title, children }: LayoutProps) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charset=\"UTF-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n        <title>{title}</title>\n        <style>{raw(globalStyles)}</style>\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n","import { Hono } from 'hono';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../../../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../../../services/BrowserService.js';\nimport type { IPageRegistry, PageEntry } from '../../../services/PageRegistry.js';\nimport { Dashboard } from '../components/Dashboard.js';\nimport { Layout } from '../components/Layout.js';\n\n/**\n * Page data for display\n */\ninterface PageData {\n  id: string;\n  url: string;\n  title: string;\n  createdAt: Date;\n}\n\n/**\n * Browser data with nested pages\n */\ninterface BrowserData {\n  id: string;\n  profileName?: string;\n  currentPageId: string | null;\n  createdAt: Date;\n  pages: PageData[];\n}\n\n/**\n * Create dashboard router for server-side rendered UI\n *\n * @param container - InversifyJS container for dependency injection\n * @returns Hono router with dashboard routes\n */\nexport function createDashboardRouter(container: Container): Hono {\n  const app = new Hono();\n\n  /**\n   * GET / - Dashboard home page with SSR\n   *\n   * Renders the dashboard UI with initial server-side data\n   */\n  app.get('/', async (c) => {\n    try {\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const pageRegistry = container.get<IPageRegistry>(PLAYWRIGHT_TYPES.PageRegistry);\n\n      const browserInstances = browserService.listBrowsers();\n      const allPages = pageRegistry.list();\n\n      // Build browser data with nested pages\n      const browsers: BrowserData[] = browserInstances.map((browser: BrowserInstance) => {\n        const browserPages = allPages.filter((p: PageEntry) => p.browserId === browser.id);\n\n        return {\n          id: browser.id,\n          profileName: browser.profileName,\n          currentPageId: browser.currentPageId,\n          createdAt: browser.createdAt,\n          pages: browserPages.map((page: PageEntry) => ({\n            id: page.id,\n            url: page.url,\n            title: page.title,\n            createdAt: page.createdAt,\n          })),\n        };\n      });\n\n      const stats = {\n        totalBrowsers: browserInstances.length,\n        totalPages: allPages.length,\n      };\n\n      // Render dashboard with Layout wrapper\n      return c.html(\n        <Layout title=\"Playwright MCP Dashboard\">\n          <Dashboard browsers={browsers} stats={stats} />\n        </Layout>,\n      );\n    } catch (error) {\n      console.error('Failed to render dashboard:', error);\n\n      return c.html(\n        <Layout title=\"Playwright MCP Dashboard - Error\">\n          <div style=\"padding: 2rem; text-align: center;\">\n            <h1>Failed to load dashboard</h1>\n            <p>{error instanceof Error ? error.message : String(error)}</p>\n            <a href=\"/\" style=\"color: #2196f3; text-decoration: underline;\">\n              Retry\n            </a>\n          </div>\n        </Layout>,\n        500,\n      );\n    }\n  });\n\n  return app;\n}\n","/**\n * Origin / Host gate for the browser-automation daemon.\n *\n * The daemon listens on loopback, which makes it reachable by any page the user\n * happens to visit: a web page can `fetch` and open WebSockets to localhost, and\n * the daemon drives a real browser holding the user's logged-in sessions. Being\n * loopback-only is therefore not a security boundary on its own.\n *\n * Browsers attach an unforgeable `Origin` to cross-origin requests and to every\n * WebSocket handshake, and `Sec-Fetch-Site` to the no-Origin cases such as\n * `<script src>` and `<img>`. Requiring one of those to look local is what keeps\n * a visited page from driving the daemon, while a local process (the MCP proxy,\n * the CLI, curl) sends neither and is let through.\n */\n\nimport type { Context, MiddlewareHandler } from 'hono';\n\n/** How a request was classified, for handlers that need to know. */\nexport type RequestClass = 'local-process' | 'same-origin' | 'extension';\n\n/** Context key holding the classification for downstream handlers. */\nexport const REQUEST_CLASS_KEY = 'browseRequestClass';\n\n/** Env var listing extra hostnames the daemon may be addressed by. */\nexport const ALLOWED_HOSTS_ENV = 'BROWSE_TOOL_ALLOWED_HOSTS';\n\n/** Set to '1' to log rejections instead of blocking them. */\nexport const ORIGIN_GUARD_DISABLED_ENV = 'BROWSE_TOOL_DISABLE_ORIGIN_GUARD';\n\nconst LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '::1', '[::1]', '0.0.0.0'];\n\nconst EXTENSION_ORIGIN_SCHEMES = ['chrome-extension://', 'moz-extension://', 'safari-web-extension://'];\n\nexport interface OriginGuardConfig {\n  /**\n   * Resolve the address the server is actually bound to. Called per request\n   * because the port is only known after the server binds.\n   */\n  resolveBinding: () => { host: string; port?: number };\n}\n\n/** Strip brackets and port so IPv6 and host:port forms compare equally. */\nfunction normalizeHostname(value: string): string {\n  const trimmed = value.trim().toLowerCase();\n\n  if (trimmed.startsWith('[')) {\n    const end = trimmed.indexOf(']');\n    return end === -1 ? trimmed : trimmed.slice(1, end);\n  }\n\n  const lastColon = trimmed.lastIndexOf(':');\n  if (lastColon !== -1 && !trimmed.slice(lastColon + 1).includes(':')) {\n    return trimmed.slice(0, lastColon);\n  }\n\n  return trimmed;\n}\n\nfunction parsePort(hostHeader: string): number | undefined {\n  const trimmed = hostHeader.trim();\n  const afterBracket = trimmed.startsWith('[') ? trimmed.slice(trimmed.indexOf(']') + 1) : trimmed;\n  const lastColon = afterBracket.lastIndexOf(':');\n  if (lastColon === -1) {\n    return undefined;\n  }\n\n  const port = Number(afterBracket.slice(lastColon + 1));\n  return Number.isInteger(port) ? port : undefined;\n}\n\nfunction allowedHostnames(boundHost: string): string[] {\n  const configured = (process.env[ALLOWED_HOSTS_ENV] ?? '')\n    .split(',')\n    .map((entry) => normalizeHostname(entry))\n    .filter((entry) => entry.length > 0);\n\n  return [...LOOPBACK_HOSTS.map(normalizeHostname), normalizeHostname(boundHost), ...configured];\n}\n\nfunction isExtensionOrigin(origin: string): boolean {\n  return EXTENSION_ORIGIN_SCHEMES.some((scheme) => origin.startsWith(scheme));\n}\n\n/** Whether an Origin header names this daemon itself. */\nexport function isSameOrigin(origin: string, boundHost: string, boundPort?: number): boolean {\n  let parsed: URL;\n  try {\n    parsed = new URL(origin);\n  } catch {\n    return false;\n  }\n\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n    return false;\n  }\n\n  if (!allowedHostnames(boundHost).includes(normalizeHostname(parsed.hostname))) {\n    return false;\n  }\n\n  const originPort = parsed.port ? Number(parsed.port) : undefined;\n  return boundPort === undefined || originPort === undefined || originPort === boundPort;\n}\n\n/** Whether a request may talk to the daemon, and as what kind of client. */\nexport function classifyRequest(\n  headers: { host?: string; origin?: string; secFetchSite?: string },\n  binding: { host: string; port?: number },\n): { allowed: true; requestClass: RequestClass } | { allowed: false; reason: string } {\n  // A forged Host is how DNS rebinding turns a public page into a local one, and\n  // it is also what the telemetry config echoes back to the extension.\n  if (headers.host !== undefined) {\n    const hostname = normalizeHostname(headers.host);\n    if (!allowedHostnames(binding.host).includes(hostname)) {\n      return { allowed: false, reason: `Host \"${headers.host}\" is not a local address for this daemon` };\n    }\n\n    const hostPort = parsePort(headers.host);\n    if (binding.port !== undefined && hostPort !== undefined && hostPort !== binding.port) {\n      return { allowed: false, reason: `Host port ${hostPort} does not match this daemon's port ${binding.port}` };\n    }\n  }\n\n  const origin = headers.origin;\n  if (origin !== undefined && origin !== 'null') {\n    if (isExtensionOrigin(origin)) {\n      return { allowed: true, requestClass: 'extension' };\n    }\n\n    if (isSameOrigin(origin, binding.host, binding.port)) {\n      return { allowed: true, requestClass: 'same-origin' };\n    }\n\n    return { allowed: false, reason: `Origin \"${origin}\" is not allowed to reach this daemon` };\n  }\n\n  // No Origin: either a local process, or a browser load that omits it such as\n  // `<script src>`. Sec-Fetch-Site is what separates those two.\n  const secFetchSite = headers.secFetchSite;\n  if (secFetchSite !== undefined && secFetchSite !== 'same-origin' && secFetchSite !== 'none') {\n    return { allowed: false, reason: `Cross-site request (Sec-Fetch-Site: ${secFetchSite}) is not allowed` };\n  }\n\n  return { allowed: true, requestClass: 'local-process' };\n}\n\n/**\n * Reject requests that a web page could have made.\n *\n * Mount before every other route, including the WebSocket upgrade — `@hono/node-ws`\n * routes upgrades through the same app, so a short-circuit here refuses the socket.\n */\nexport function createOriginGuard(config: OriginGuardConfig): MiddlewareHandler {\n  return async (c, next) => {\n    const binding = config.resolveBinding();\n    const verdict = classifyRequest(\n      {\n        host: c.req.header('host'),\n        origin: c.req.header('origin'),\n        secFetchSite: c.req.header('sec-fetch-site'),\n      },\n      binding,\n    );\n\n    if (!verdict.allowed) {\n      if (process.env[ORIGIN_GUARD_DISABLED_ENV] === '1') {\n        console.warn(`[OriginGuard] Allowing blocked request (guard disabled): ${verdict.reason}`);\n        c.set(REQUEST_CLASS_KEY, 'local-process');\n        return next();\n      }\n\n      console.warn(`[OriginGuard] Rejected ${c.req.method} ${c.req.path}: ${verdict.reason}`);\n      return c.json({ error: 'forbidden', reason: verdict.reason }, 403);\n    }\n\n    c.set(REQUEST_CLASS_KEY, verdict.requestClass);\n    return next();\n  };\n}\n\n/** Read the classification stored by {@link createOriginGuard}. */\nexport function getRequestClass(c: Context): RequestClass | undefined {\n  return c.get(REQUEST_CLASS_KEY) as RequestClass | undefined;\n}\n","/**\n * HTTP Server for Browser Automation\n *\n * DESIGN PATTERNS:\n * - Hono for HTTP routing and middleware\n * - Dependency injection with InversifyJS Container\n * - Service layer for business logic\n * - Generic execute endpoint for all tools\n *\n * CODING STANDARDS:\n * - Use Hono for HTTP routing and middleware\n * - Use CORS middleware for cross-origin requests\n * - Keep route handlers thin, delegate to services\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n */\n\nimport { coerceArgs, formatZodError } from '@agimon-ai/foundation-validator';\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport type { Container } from 'inversify';\nimport { z } from 'zod';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { BrowserInstance, IBrowserService } from '../services/BrowserService.js';\nimport { CustomToolService } from '../services/CustomToolService.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport type { IPageRegistry } from '../services/PageRegistry.js';\nimport type { IStealthLauncher } from '../services/StealthLauncher.js';\nimport { type ITelemetryService, TelemetryService } from '../services/TelemetryService.js';\nimport type { Tool } from '../types/index.js';\nimport { getPlaywrightHost } from '../utils/networkConfig.js';\nimport { OWNER_HEADER, runWithOwner } from '../utils/ownerContext.js';\nimport { getPackageVersion } from '../utils/packageVersion.js';\nimport { runWithTelemetryContext, TELEMETRY_HEADERS, telemetryContextFromHeaders } from '../utils/telemetryContext.js';\nimport { createApiRouter } from './dashboard/routes/api.js';\nimport { createDashboardRouter } from './dashboard/routes/dashboard.js';\nimport { createExtensionRoutes } from './extension-routes.js';\nimport { createOriginGuard, isSameOrigin } from './middleware/originGuard.js';\n\n/**\n * Execute request body\n */\ninterface ExecuteRequest {\n  tool: string;\n  arguments: Record<string, unknown>;\n}\n\n/**\n * Execute response body\n */\ninterface ExecuteResponse {\n  success: boolean;\n  result?: CallToolResult;\n  error?: string;\n}\n\nconst CALLER_VALIDATION_ERROR_TYPE = 'caller_validation';\n\ninterface CustomToolsResponse {\n  tools: Array<{\n    name: string;\n    description: string;\n    suggestionActions?: string;\n    inputSchema: Record<string, unknown>;\n    capabilities: Record<string, unknown>;\n  }>;\n  error?: string;\n}\n\n/**\n * Browser info for health response\n */\ninterface BrowserInfo {\n  id: string;\n  pageCount: number;\n  createdAt: string;\n}\n\n/**\n * Health response\n */\ninterface HealthResponse {\n  status: 'healthy' | 'unhealthy';\n  service: string;\n  serviceName: string;\n  /** Package version of the running daemon, so clients can detect version skew. */\n  version: string;\n  timestamp: string;\n  browsers: {\n    count: number;\n    instances: BrowserInfo[];\n  };\n}\n\n/**\n * Options for wiring the daemon's lifecycle into its HTTP surface.\n */\nexport interface HttpServerOptions {\n  /**\n   * Resolve the address the server is bound to, used to decide which origins\n   * count as local. Called per request because the port is assigned after the\n   * app is built.\n   */\n  resolveBinding?: () => { host: string; port?: number };\n  /**\n   * Invoked by `POST /shutdown`. When omitted the route reports 404, so a\n   * process that cannot shut itself down does not advertise that it can.\n   */\n  onShutdownRequested?: () => void;\n  /**\n   * Invoked for every request that does real work, so the daemon can tell an\n   * idle lifetime from a busy one. Health probes are excluded: discovery polls\n   * them on daemons it never goes on to use.\n   */\n  onRequestServed?: () => void;\n}\n\nfunction getResultError(result: CallToolResult): string | undefined {\n  if (!result.isError) {\n    return undefined;\n  }\n\n  const firstContent = result.content[0];\n  if (firstContent?.type === 'text' && typeof firstContent.text === 'string') {\n    return firstContent.text;\n  }\n\n  return 'Unknown tool execution error';\n}\n\ninterface StructuredBrowserError {\n  code: string;\n  retryable: boolean;\n  targetKey?: string;\n  targetState: string;\n  retryAfterMs: number;\n}\n\ntype ToolErrorCategory = 'caller_execution' | 'infrastructure_failure' | 'page_state' | 'spec_failure' | 'tool_failure';\n\ninterface StructuredToolOutcome {\n  category: string;\n  failedTests?: number;\n  passedTests?: number;\n  timings?: {\n    executionMs?: number;\n    hooksMs?: number;\n    totalMs?: number;\n  };\n}\n\nfunction getStructuredBrowserError(result: CallToolResult): StructuredBrowserError | undefined {\n  const structured = result.structuredContent;\n  const error = structured && typeof structured === 'object' && 'error' in structured ? structured.error : undefined;\n  if (\n    typeof error !== 'object' ||\n    error === null ||\n    !('code' in error) ||\n    typeof error.code !== 'string' ||\n    !('retryable' in error) ||\n    typeof error.retryable !== 'boolean' ||\n    !('targetState' in error) ||\n    typeof error.targetState !== 'string' ||\n    !('retryAfterMs' in error) ||\n    typeof error.retryAfterMs !== 'number'\n  ) {\n    return undefined;\n  }\n\n  return {\n    code: error.code,\n    retryable: error.retryable,\n    targetKey: 'targetKey' in error && typeof error.targetKey === 'string' ? error.targetKey : undefined,\n    targetState: error.targetState,\n    retryAfterMs: error.retryAfterMs,\n  };\n}\n\nfunction getStructuredToolOutcome(result: CallToolResult): StructuredToolOutcome | undefined {\n  const structured = result.structuredContent;\n  const outcome =\n    structured && typeof structured === 'object' && 'outcome' in structured ? structured.outcome : undefined;\n  if (typeof outcome !== 'object' || outcome === null || !('category' in outcome)) {\n    return undefined;\n  }\n\n  return outcome as StructuredToolOutcome;\n}\n\nfunction classifyToolError(toolName: string, result: CallToolResult, errorMessage: string): ToolErrorCategory {\n  const outcome = getStructuredToolOutcome(result);\n  if (outcome?.category === 'spec_failure') {\n    return 'spec_failure';\n  }\n  if (getStructuredBrowserError(result)) {\n    return 'infrastructure_failure';\n  }\n  if (/element not found|stale (?:element|reference)|page .* not found/i.test(errorMessage)) {\n    return 'page_state';\n  }\n  if (\n    (toolName === 'browser_evaluate_script' || toolName === 'browser_run_code') &&\n    /SyntaxError|Illegal return statement|Unexpected token/i.test(errorMessage)\n  ) {\n    return 'caller_execution';\n  }\n  return 'tool_failure';\n}\n\nfunction isOperationalToolFailure(category: ToolErrorCategory): boolean {\n  return category === 'infrastructure_failure' || category === 'tool_failure';\n}\n\nfunction resolveTelemetryService(container: Container): ITelemetryService {\n  try {\n    return container.get<ITelemetryService>(PLAYWRIGHT_TYPES.TelemetryService);\n  } catch {\n    return new TelemetryService();\n  }\n}\n\n/**\n * Refreshes idle timestamps after a tool ran against a page.\n * Stealth-mode browsers live in StealthLauncher's registry rather than\n * BrowserService's, so fall back to touching them there.\n */\nfunction recordPageActivity(container: Container, pageRegistry: IPageRegistry, pageId: string): void {\n  const pageEntry = pageRegistry.get(pageId);\n  if (!pageEntry) {\n    return;\n  }\n\n  const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n  if (browserService.recordBrowserActivity(pageEntry.browserId, pageId)) {\n    return;\n  }\n\n  try {\n    const stealthLauncher = container.get<IStealthLauncher>(PLAYWRIGHT_TYPES.StealthLauncher);\n    if (stealthLauncher.touch(pageEntry.browserId)) {\n      pageRegistry.touchPage(pageId);\n    }\n  } catch {\n    // StealthLauncher is not bound in every container (e.g. the MCP proxy container)\n  }\n}\n\nfunction normalizeBuiltInToolName(toolName: string): string {\n  if (toolName.startsWith('browser-') || toolName === 'run-spec' || toolName === 'discover-specs') {\n    return toolName.replaceAll('-', '_');\n  }\n\n  return toolName;\n}\n\nfunction getStaleToolReplacement(toolName: string): string | undefined {\n  const staleToolNames: Record<string, string> = {\n    browser_console_logs: 'browser_list_console_messages',\n    browser_console_messages: 'browser_list_console_messages',\n    browser_evaluate: 'browser_evaluate_script',\n    browser_get_console_logs: 'browser_list_console_messages',\n    browser_resize: 'browser_resize_page',\n    browser_take_screenshot: 'browser_screenshot',\n    browser_list_session: 'browser_list_pages',\n  };\n\n  return staleToolNames[toolName];\n}\n\n/**\n * Create HTTP server for browser automation\n *\n * @param container - InversifyJS container with services\n * @returns Configured Hono app\n */\nexport function createHttpServer(container: Container, options: HttpServerOptions = {}): Hono {\n  const app = new Hono();\n  // Flipped as soon as shutdown starts so discoverers stop reusing a dying daemon.\n  let shuttingDown = false;\n  const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n    PLAYWRIGHT_TYPES.PageRegistry,\n  );\n  const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n  const extensionTaskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n  const telemetry = resolveTelemetryService(container);\n  const customToolService = new CustomToolService(pageRegistry, extensionTaskQueue, telemetry, browserService);\n\n  // First gate: refuse anything a web page could have sent. This runs before\n  // every route, including the WebSocket upgrade.\n  const resolveBinding: NonNullable<HttpServerOptions['resolveBinding']> =\n    options.resolveBinding ?? (() => ({ host: getPlaywrightHost() }));\n  app.use('*', createOriginGuard({ resolveBinding }));\n\n  // CORS reflects only origins the guard above would accept. Reflecting any\n  // origin with credentials is what let a visited page read daemon responses.\n  app.use(\n    '*',\n    cors({\n      origin: (origin) => {\n        if (!origin) {\n          return null;\n        }\n        const binding = resolveBinding();\n        const allowed =\n          origin.startsWith('chrome-extension://') ||\n          origin.startsWith('moz-extension://') ||\n          isSameOrigin(origin, binding.host, binding.port);\n        return allowed ? origin : null;\n      },\n      credentials: true,\n      allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],\n      allowHeaders: ['Content-Type', 'Accept', 'Authorization', OWNER_HEADER, ...TELEMETRY_HEADERS],\n    }),\n  );\n\n  // Attribute every record this request produces to the calling agent, rather\n  // than to whichever agent's env this shared daemon happened to inherit.\n  // Wrapping as middleware covers handler bodies, error paths, and crash logs\n  // alike, since AsyncLocalStorage propagates across the awaited `next()`.\n  app.use('*', (c, next) =>\n    runWithTelemetryContext(\n      telemetryContextFromHeaders((name) => c.req.header(name)),\n      next,\n    ),\n  );\n\n  // Registered after the guards so a rejected request never counts as work.\n  const recordRequest = options.onRequestServed;\n  if (recordRequest) {\n    app.use('*', async (c, next) => {\n      if (c.req.path !== '/health') {\n        recordRequest();\n      }\n      await next();\n    });\n  }\n\n  // Health check endpoint\n  app.get('/health', (c) => {\n    try {\n      if (shuttingDown) {\n        return c.json(\n          {\n            status: 'unhealthy',\n            service: 'browse-tool-http',\n            serviceName: 'browse-tool-http',\n            version: getPackageVersion(),\n            error: 'Server is shutting down',\n          },\n          503,\n        );\n      }\n\n      const browsers = browserService.listBrowsers();\n\n      const response: HealthResponse = {\n        status: 'healthy',\n        service: 'browse-tool-http',\n        serviceName: 'browse-tool-http',\n        version: getPackageVersion(),\n        timestamp: new Date().toISOString(),\n        browsers: {\n          count: browsers.length,\n          instances: browsers.map((b: BrowserInstance) => ({\n            id: b.id,\n            pageCount: b.pageIds.size,\n            createdAt: b.createdAt.toISOString(),\n          })),\n        },\n      };\n\n      return c.json(response);\n    } catch (error) {\n      return c.json(\n        {\n          status: 'unhealthy',\n          service: 'browse-tool-http',\n          serviceName: 'browse-tool-http',\n          version: getPackageVersion(),\n          error: error instanceof Error ? error.message : String(error),\n        },\n        503,\n      );\n    }\n  });\n\n  // Graceful shutdown endpoint. Letting the daemon close its own browsers is the\n  // only way to stop cleanly; signalling it from outside races browser teardown.\n  app.post('/shutdown', (c) => {\n    const requestShutdown = options.onShutdownRequested;\n    if (!requestShutdown) {\n      return c.json({ error: 'Shutdown endpoint is not available on this server' }, 404);\n    }\n\n    shuttingDown = true;\n    // Respond before tearing down, so the caller learns the request was accepted.\n    setImmediate(requestShutdown);\n    return c.json({ status: 'shutting-down' }, 202);\n  });\n\n  // Execute tool endpoint\n  app.post('/execute', async (c) => {\n    try {\n      const body = (await c.req.json()) as ExecuteRequest;\n\n      if (!body.tool) {\n        return c.json<ExecuteResponse>(\n          {\n            success: false,\n            error: 'Missing \"tool\" field in request body',\n          },\n          400,\n        );\n      }\n\n      const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n      return await telemetry.runInSpan(\n        'browse_tool.http.execute',\n        {\n          attributes: {\n            'http.method': 'POST',\n            'http.route': '/execute',\n            'browse_tool.tool.name': body.tool,\n            'browse_tool.page.id': pageId,\n          },\n        },\n        async (span) => {\n          const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n          const normalizedToolName = normalizeBuiltInToolName(body.tool);\n          const tool = tools.find((t) => t.getDefinition().name === normalizedToolName);\n\n          if (process.env.BROWSE_TOOL_DEBUG_EXTENSION_RECORDING === '1' && body.tool === 'browser_launch') {\n            const videoDir =\n              typeof body.arguments?.videoDir === 'string'\n                ? body.arguments.videoDir\n                : typeof body.arguments?.video_dir === 'string'\n                  ? body.arguments.video_dir\n                  : '';\n            telemetry.log('debug', 'browse-tool extension recording launch', {\n              attributes: {\n                'browse_tool.launch.mode': typeof body.arguments?.mode === 'string' ? body.arguments.mode : '',\n                'browse_tool.recording.video_dir': videoDir,\n              },\n            });\n          }\n\n          if (!tool) {\n            const replacement = getStaleToolReplacement(normalizedToolName);\n            const errorMessage = replacement\n              ? `Tool \"${body.tool}\" not found. Use \"${replacement}\" instead.`\n              : `Tool \"${body.tool}\" not found`;\n            span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n            telemetry.log('warn', 'browse-tool HTTP execute rejected unknown tool', {\n              attributes: {\n                'http.route': '/execute',\n                'browse_tool.tool.name': body.tool,\n                'browse_tool.tool.normalized_name': normalizedToolName,\n                'browse_tool.tool.suggested_name': replacement,\n              },\n            });\n            return c.json<ExecuteResponse>(\n              {\n                success: false,\n                error: errorMessage,\n              },\n              404,\n            );\n          }\n\n          const rawArgs = body.arguments || {};\n          let parsedArgs: Record<string, unknown>;\n          try {\n            parsedArgs = tool.getInputSchema().parse(coerceArgs(rawArgs, tool.getInputSchema()));\n          } catch (error) {\n            if (error instanceof z.ZodError) {\n              const validationMessage = formatZodError(error, {\n                schemaName: normalizedToolName,\n                schema: tool.getInputSchema(),\n              });\n              const validationAttributes = {\n                'error.type': CALLER_VALIDATION_ERROR_TYPE,\n                'browse_tool.error.category': CALLER_VALIDATION_ERROR_TYPE,\n                'browse_tool.validation.failed': true,\n                'browse_tool.validation.issue_count': error.issues.length,\n              };\n              span?.setAttributes(validationAttributes);\n              telemetry.log('warn', 'browse-tool HTTP execute rejected invalid arguments', {\n                attributes: {\n                  'http.route': '/execute',\n                  'browse_tool.tool.name': normalizedToolName,\n                  'browse_tool.page.id': pageId,\n                  ...validationAttributes,\n                },\n              });\n              return c.json<ExecuteResponse>({\n                success: false,\n                result: {\n                  content: [{ type: 'text', text: validationMessage }],\n                  isError: true,\n                },\n                error: validationMessage,\n              });\n            }\n            throw error;\n          }\n\n          const args = parsedArgs;\n          const requestPageId = typeof args.pageId === 'string' ? args.pageId : undefined;\n\n          // Record activity before AND after the tool runs. A long-running tool\n          // would otherwise leave the browser looking idle for its whole\n          // duration and could be reaped mid-call.\n          if (requestPageId) {\n            recordPageActivity(container, pageRegistry, requestPageId);\n          }\n\n          let result: CallToolResult;\n          try {\n            result = await runWithOwner(c.req.header(OWNER_HEADER), () => tool.execute(parsedArgs));\n          } catch (error) {\n            result = {\n              content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n              isError: true,\n            };\n          }\n\n          if (requestPageId) {\n            recordPageActivity(container, pageRegistry, requestPageId);\n          }\n\n          const errorMessage = getResultError(result);\n          if (errorMessage) {\n            const structuredError = getStructuredBrowserError(result);\n            const outcome = getStructuredToolOutcome(result);\n            const errorCategory = classifyToolError(normalizedToolName, result, errorMessage);\n            const operationalFailure = isOperationalToolFailure(errorCategory);\n            const errorAttributes = structuredError\n              ? {\n                  'browser.error.code': structuredError.code,\n                  'browser.error.retryable': structuredError.retryable,\n                  'browser.error.retry_after_ms': structuredError.retryAfterMs,\n                  'browser.target.key': structuredError.targetKey,\n                  'browser.target.state': structuredError.targetState,\n                }\n              : {};\n            const classificationAttributes = {\n              'browse_tool.error.category': errorCategory,\n              'tool.outcome.failed': true,\n              'tool.result.error': operationalFailure,\n              ...(outcome?.timings\n                ? {\n                    'browse_tool.spec.execution_ms': outcome.timings.executionMs,\n                    'browse_tool.spec.hooks_ms': outcome.timings.hooksMs,\n                    'browse_tool.spec.total_ms': outcome.timings.totalMs,\n                    'browse_tool.spec.failed_tests': outcome.failedTests,\n                    'browse_tool.spec.passed_tests': outcome.passedTests,\n                  }\n                : {}),\n            };\n            span?.setAttributes({ ...classificationAttributes, ...errorAttributes });\n            if (operationalFailure) {\n              span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n            }\n            telemetry.log(\n              'warn',\n              operationalFailure\n                ? 'browse-tool HTTP execute returned tool error'\n                : 'browse-tool HTTP execute returned expected failed outcome',\n              {\n                attributes: {\n                  'http.route': '/execute',\n                  'browse_tool.tool.name': normalizedToolName,\n                  'browse_tool.page.id': requestPageId,\n                  'tool.name': normalizedToolName,\n                  'error.message': errorMessage,\n                  ...classificationAttributes,\n                  ...errorAttributes,\n                },\n              },\n            );\n          } else {\n            telemetry.log('debug', 'browse-tool HTTP execute succeeded', {\n              attributes: {\n                'http.route': '/execute',\n                'browse_tool.tool.name': normalizedToolName,\n                'browse_tool.page.id': requestPageId,\n              },\n            });\n          }\n\n          return c.json<ExecuteResponse>({\n            success: !result.isError,\n            result,\n            error: errorMessage,\n          });\n        },\n      );\n    } catch (error) {\n      telemetry.log('error', 'browse-tool HTTP execute request crashed', {\n        attributes: {\n          'http.route': '/execute',\n        },\n        exception: error,\n      });\n      return c.json<ExecuteResponse>(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  // List browsers endpoint\n  app.get('/browsers', (c) => {\n    try {\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const browsers = browserService.listBrowsers();\n\n      return c.json({\n        browsers: browsers.map((b: BrowserInstance) => ({\n          id: b.id,\n          profileName: b.profileName,\n          pageIds: Array.from(b.pageIds),\n          currentPageId: b.currentPageId,\n          createdAt: b.createdAt.toISOString(),\n        })),\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  // List tools endpoint\n  app.get('/tools', (c) => {\n    try {\n      const tools = container.getAll<Tool>(PLAYWRIGHT_TYPES.Tool);\n\n      return c.json({\n        tools: tools.map((t) => t.getDefinition()),\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  app.get('/custom-tools', async (c) => {\n    const directory = c.req.query('dir');\n\n    if (!directory) {\n      return c.json<CustomToolsResponse>({ tools: [], error: 'Missing \"dir\" query parameter' }, 400);\n    }\n\n    try {\n      const tools = await customToolService.listTools(directory);\n      return c.json<CustomToolsResponse>({ tools });\n    } catch (error) {\n      return c.json<CustomToolsResponse>(\n        {\n          tools: [],\n          error: error instanceof Error ? error.message : String(error),\n        },\n        400,\n      );\n    }\n  });\n\n  app.post('/custom-tools', async (c) => {\n    const directory = c.req.query('dir');\n\n    if (!directory) {\n      return c.json<ExecuteResponse>({ success: false, error: 'Missing \"dir\" query parameter' }, 400);\n    }\n\n    try {\n      const body = (await c.req.json()) as ExecuteRequest;\n\n      if (!body.tool) {\n        return c.json<ExecuteResponse>({ success: false, error: 'Missing \"tool\" field in request body' }, 400);\n      }\n\n      const pageId = typeof body.arguments?.pageId === 'string' ? body.arguments.pageId : undefined;\n\n      return await telemetry.runInSpan(\n        'browse_tool.http.custom_tool.execute',\n        {\n          attributes: {\n            'http.method': 'POST',\n            'http.route': '/custom-tools',\n            'browse_tool.tool.name': body.tool,\n            'browse_tool.page.id': pageId,\n            'browse_tool.custom_tools.directory': directory,\n          },\n        },\n        async (span) => {\n          // Mirror /execute: a throwing tool is a tool error the agent can act\n          // on, not a transport crash. An ownership refusal is the common case.\n          let result: CallToolResult;\n          if (pageId) {\n            recordPageActivity(container, pageRegistry, pageId);\n          }\n\n          try {\n            result = await runWithOwner(c.req.header(OWNER_HEADER), () =>\n              customToolService.executeTool(directory, body.tool, body.arguments || {}),\n            );\n          } catch (error) {\n            result = {\n              content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n              isError: true,\n            };\n          }\n\n          if (pageId) {\n            recordPageActivity(container, pageRegistry, pageId);\n          }\n\n          const errorMessage = getResultError(result);\n          if (errorMessage) {\n            span?.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });\n            telemetry.log('warn', 'browse-tool custom tool execution returned tool error', {\n              attributes: {\n                'http.route': '/custom-tools',\n                'browse_tool.tool.name': body.tool,\n                'browse_tool.page.id': pageId,\n                'tool.result.error': true,\n                'tool.name': body.tool,\n                'error.message': errorMessage,\n              },\n            });\n          } else {\n            telemetry.log('debug', 'browse-tool custom tool execution succeeded', {\n              attributes: {\n                'http.route': '/custom-tools',\n                'browse_tool.tool.name': body.tool,\n                'browse_tool.page.id': pageId,\n              },\n            });\n          }\n\n          return c.json<ExecuteResponse>({\n            success: !result.isError,\n            result,\n            error: errorMessage,\n          });\n        },\n      );\n    } catch (error) {\n      telemetry.log('error', 'browse-tool custom tool request crashed', {\n        attributes: {\n          'http.route': '/custom-tools',\n          'browse_tool.custom_tools.directory': directory,\n        },\n        exception: error,\n      });\n      return c.json<ExecuteResponse>(\n        {\n          success: false,\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n\n  // Mount extension routes for Chrome extension communication\n  const extensionRouter = createExtensionRoutes(container);\n  app.route('/extension', extensionRouter);\n\n  // Mount dashboard API routes\n  const apiRouter = createApiRouter(container);\n  app.route('/api', apiRouter);\n\n  // Mount dashboard UI routes (SSR)\n  const dashboardRouter = createDashboardRouter(container);\n  app.route('/', dashboardRouter);\n\n  return app;\n}\n","/**\n * WebSocket Routes for Extension Communication\n *\n * DESIGN PATTERNS:\n * - WebSocket upgrade handler using @hono/node-ws\n * - Dependency injection with InversifyJS Container\n * - Pub/sub pattern via WebSocketHub\n *\n * CODING STANDARDS:\n * - Use Hono WebSocket helpers for upgrade\n * - Keep route handlers thin, delegate to WebSocketHub\n * - Handle connection lifecycle properly\n *\n * AVOID:\n * - Business logic in route handlers\n * - Missing error handling\n * - Memory leaks from uncleaned connections\n */\n\nimport type { Hono } from 'hono';\nimport type { UpgradeWebSocket } from 'hono/ws';\nimport type { Container } from 'inversify';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\n\n/**\n * Setup WebSocket routes for extension communication\n *\n * @param app - Hono app instance\n * @param container - InversifyJS container with services\n * @param upgradeWebSocket - WebSocket upgrade function from @hono/node-ws\n */\nexport function setupWebSocketRoutes(app: Hono, container: Container, upgradeWebSocket: UpgradeWebSocket): void {\n  /**\n   * WebSocket endpoint for extension connections\n   * Path: /ws/extension/:browserId\n   */\n  app.get(\n    '/ws/extension/:browserId',\n    upgradeWebSocket((c) => {\n      const browserId = c.req.param('browserId');\n      let connectionId: string | null = null;\n\n      return {\n        onOpen(_event, ws) {\n          if (!browserId) {\n            ws.close(1008, 'Missing browserId');\n            return;\n          }\n          try {\n            const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n            connectionId = wsHub.addConnection(ws, browserId);\n          } catch {\n            ws.close(1011, 'Internal server error');\n          }\n        },\n\n        onMessage(event) {\n          if (!connectionId) {\n            return;\n          }\n\n          try {\n            const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n            const data = typeof event.data === 'string' ? event.data : event.data.toString();\n            wsHub.handleMessage(connectionId, data);\n          } catch {\n            // Error handling delegated to WebSocketHub\n          }\n        },\n\n        onClose() {\n          if (connectionId) {\n            try {\n              const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n              wsHub.removeConnection(connectionId);\n            } catch {\n              // Ignore cleanup errors\n            }\n          }\n        },\n\n        onError() {\n          if (connectionId) {\n            try {\n              const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n              wsHub.removeConnection(connectionId);\n            } catch {\n              // Ignore cleanup errors\n            }\n          }\n        },\n      };\n    }),\n  );\n\n  /**\n   * GET /ws/stats\n   * Get WebSocket connection statistics\n   */\n  app.get('/ws/stats', (c) => {\n    try {\n      const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n      const stats = wsHub.getStats();\n\n      return c.json({\n        totalConnections: stats.totalConnections,\n        browserCount: stats.browserCount,\n        connections: stats.connections.map((conn) => ({\n          id: conn.id,\n          browserId: conn.browserId,\n          connectedAt: conn.connectedAt.toISOString(),\n        })),\n      });\n    } catch (error) {\n      return c.json(\n        {\n          error: error instanceof Error ? error.message : String(error),\n        },\n        500,\n      );\n    }\n  });\n}\n","/**\n * Startup sweep for browser temp directories.\n *\n * Browser launches create temp directories that are removed when the browser\n * closes, but a daemon that is SIGKILLed or crashes never gets to do that. The\n * daemon is shared and long-lived, so those leftovers accumulate — including\n * proxy-auth extensions holding plaintext credentials.\n *\n * Another daemon may be running concurrently, so age is the only safe way to\n * tell an abandoned directory from one currently in use.\n */\n\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { STEALTH_USER_DATA_PREFIX } from '../services/StealthLauncher.js';\nimport { PROXY_AUTH_EXTENSION_PREFIX } from './proxyAuthExtension.js';\n\n/** Prefix used for generated extension-mode user-data directories. */\nexport const EXTENSION_USER_DATA_PREFIX = 'extension-chrome-';\n\nconst HOUR_MS = 60 * 60 * 1000;\n\n/** Directories holding credentials expire sooner than plain browser profiles. */\nconst SWEEP_RULES: ReadonlyArray<{ prefix: string; maxAgeMs: number }> = [\n  { prefix: PROXY_AUTH_EXTENSION_PREFIX, maxAgeMs: 6 * HOUR_MS },\n  { prefix: STEALTH_USER_DATA_PREFIX, maxAgeMs: 24 * HOUR_MS },\n  { prefix: EXTENSION_USER_DATA_PREFIX, maxAgeMs: 24 * HOUR_MS },\n];\n\n/**\n * Remove abandoned browser temp directories.\n *\n * Never throws: a sweep failure must not stop the daemon from starting.\n *\n * @returns Number of directories removed\n */\nexport async function cleanupOrphanedBrowserTempDirs(now = Date.now()): Promise<number> {\n  const tempRoot = os.tmpdir();\n  let removed = 0;\n\n  let entries: string[];\n  try {\n    entries = await fs.readdir(tempRoot);\n  } catch {\n    return 0;\n  }\n\n  for (const entry of entries) {\n    const rule = SWEEP_RULES.find((candidate) => entry.startsWith(candidate.prefix));\n    if (!rule) {\n      continue;\n    }\n\n    const target = path.join(tempRoot, entry);\n    try {\n      const stats = await fs.stat(target);\n      if (!stats.isDirectory() || now - stats.mtimeMs < rule.maxAgeMs) {\n        continue;\n      }\n\n      await fs.rm(target, { recursive: true, force: true });\n      removed += 1;\n    } catch {\n      // Skip anything we cannot stat or remove; the next sweep retries.\n    }\n  }\n\n  if (removed > 0) {\n    console.log(`[TempCleanup] Removed ${removed} orphaned browser temp director${removed === 1 ? 'y' : 'ies'}`);\n  }\n\n  return removed;\n}\n","/**\n * HTTP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n * - Dependency injection with InversifyJS Container\n * - Graceful shutdown pattern for SIGINT/SIGTERM\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n * - Implement graceful shutdown for server cleanup\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n * - Missing signal handlers for graceful shutdown\n */\n\nimport path from 'node:path';\nimport { DEFAULT_PORT_RANGE, PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n  createProcessLease,\n  type ProcessLease,\n  ProcessRegistryService,\n  resolveSiblingRegistryPath,\n} from '@agimon-ai/foundation-process-registry';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createHttpContainer } from '../container/index.js';\nimport { createHttpServer } from '../server/http.js';\nimport { setupWebSocketRoutes } from '../server/websocket-routes.js';\nimport type { BrowserProcessRegistry } from '../services/BrowserProcessRegistry.js';\nimport type { IBrowserService } from '../services/BrowserService.js';\nimport type { IExtensionSessionRegistry } from '../services/ExtensionSessionRegistry.js';\nimport type { ExtensionTaskQueue } from '../services/ExtensionTaskQueue.js';\nimport { CLAIM_TOKEN_ENV } from '../services/HttpServerManager.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR, type IIdleCleanupService } from '../services/IdleCleanupService.js';\nimport { PROXY_CONFIG_DIR_ENV_VAR } from '../services/ProxyConfigService.js';\nimport type { IStealthLauncher } from '../services/StealthLauncher.js';\nimport type { IWebServerManager } from '../services/WebServerManager.js';\nimport type { WebSocketHub } from '../services/WebSocketHub.js';\nimport { buildPlaywrightBaseUrl, DEFAULT_MCP_PORT, getPlaywrightHost } from '../utils/networkConfig.js';\nimport { getPackageVersion } from '../utils/packageVersion.js';\nimport { resolveSelfProcessIdentity } from '../utils/processIdentity.js';\nimport { cleanupOrphanedBrowserTempDirs } from '../utils/tempDirCleanup.js';\nimport { BROWSE_TOOL_WORKSPACE_ROOT_ENV, resolveWorkspaceRoot, setWorkspaceRootEnv } from '../utils/workspaceRoot.js';\n\ninterface RegisterSelfOptions {\n  portRegistry: PortRegistryService;\n  repositoryPath: string;\n  serviceName: string;\n  environment: string;\n  requestedPort: number;\n  host: string;\n  metadata: Record<string, unknown>;\n}\n\n/** Whether a browse-tool daemon is already answering on this port. */\nasync function isDaemonServing(host: string, port: number): Promise<boolean> {\n  try {\n    const response = await fetch(`${buildPlaywrightBaseUrl(host, port)}/health`, {\n      signal: AbortSignal.timeout(2_000),\n    });\n    if (!response.ok) {\n      return false;\n    }\n    const body = (await response.json()) as { status?: string; service?: string };\n    return body.status === 'healthy' && body.service === 'browse-tool-http';\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Take ownership of this daemon's registry entry.\n *\n * When spawned by a client, the port was already claimed on our behalf and we\n * only confirm it. Losing that claim means another daemon now owns the scope, so\n * we exit rather than registering over it. When started by hand there is no\n * claim, and we refuse to displace a daemon that is already serving.\n *\n * @returns The port this daemon owns\n */\nasync function registerSelf(options: RegisterSelfOptions): Promise<number> {\n  const { portRegistry, repositoryPath, serviceName, environment, requestedPort, host, metadata } = options;\n  const scope = { repositoryPath, serviceName, serviceType: 'tool' as const, environment };\n  const claimToken = process.env[CLAIM_TOKEN_ENV];\n\n  if (claimToken) {\n    const confirmed = await portRegistry.confirmPort({\n      ...scope,\n      claimToken,\n      pid: process.pid,\n      metadata: { ...metadata, healthCheckUrl: `${buildPlaywrightBaseUrl(host, requestedPort)}/health` },\n    });\n\n    if (!confirmed.success || confirmed.port === undefined) {\n      console.error(`CLAIM_LOST: port ${requestedPort} is no longer ours (${confirmed.error}). Exiting.`);\n      process.exit(1);\n    }\n\n    return confirmed.port;\n  }\n\n  const existing = await portRegistry.getPort(scope);\n  const existingPort = existing.record?.port;\n  if (existingPort !== undefined && (await isDaemonServing(host, existingPort))) {\n    console.error(\n      `A browse-tool daemon is already running on port ${existingPort}. ` +\n        'Use it, or run `browse-tool stop` before starting another.',\n    );\n    process.exit(1);\n  }\n\n  const reserved = await portRegistry.reservePort({\n    ...scope,\n    preferredPort: requestedPort,\n    pid: process.pid,\n    host,\n    portRange: DEFAULT_PORT_RANGE,\n    metadata,\n  });\n\n  if (!reserved.success || reserved.port === undefined) {\n    throw new Error(\n      reserved.error || `Failed to reserve port in range ${DEFAULT_PORT_RANGE.min}-${DEFAULT_PORT_RANGE.max}`,\n    );\n  }\n\n  return reserved.port;\n}\n\ninterface HttpServeOptions {\n  port: string;\n  headless: boolean;\n  idleTimeout: string;\n  host: string;\n  registryDir?: string;\n  registryPath?: string;\n  pidsDir?: string;\n  profilesDir?: string;\n  snippetsDir?: string;\n  proxyConfigDir?: string;\n  workspaceRoot?: string;\n}\n\n/**\n * Start HTTP server for browser automation\n */\nexport const httpServeCommand = new Command('http-serve')\n  .description('Start HTTP server for browser automation')\n  .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_MCP_PORT))\n  .option('--host <host>', 'Host to bind', getPlaywrightHost())\n  .option('--headless', 'Run browsers in headless mode by default', true)\n  .option('--no-headless', 'Run browsers in headed mode (visible window)')\n  .option('--idle-timeout <minutes>', 'Idle timeout in minutes before closing browsers', '30')\n  .option('--registry-dir <path>', 'Custom registry path or directory for service discovery')\n  .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n  .option('--pids-dir <path>', 'Custom PIDs directory for process tracking (deprecated)')\n  .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n  .option('--snippets-dir <path>', 'Directory used by browser_run_code to save and load reusable snippets')\n  .option('--proxy-config-dir <path>', 'Custom proxy config directory')\n  .option(\n    '--workspace-root <path>',\n    `Canonical workspace root for registry scoping (${BROWSE_TOOL_WORKSPACE_ROOT_ENV})`,\n  )\n  .action(async function (this: Command, options: HttpServeOptions) {\n    const commandDefaults = getCommandConfig<{\n      port?: number;\n      headless?: boolean;\n      idleTimeout?: number;\n      host?: string;\n      registryDir?: string;\n      registryPath?: string;\n      pidsDir?: string;\n      profilesDir?: string;\n      snippetsDir?: string;\n      proxyConfigDir?: string;\n      workspaceRoot?: string;\n    }>('httpServe');\n    const resolvedOptions: HttpServeOptions = {\n      port: resolveConfiguredOption(\n        this,\n        'port',\n        options.port,\n        commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n        process.env.PLAYWRIGHT_PORT,\n      ),\n      headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n      idleTimeout: resolveConfiguredOption(\n        this,\n        'idleTimeout',\n        options.idleTimeout,\n        commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n        process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n      ),\n      host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n      registryDir: resolveConfiguredOption(\n        this,\n        'registryDir',\n        options.registryDir,\n        commandDefaults.registryDir,\n        process.env.PLAYWRIGHT_REGISTRY_DIR,\n      ),\n      registryPath: resolveConfiguredOption(\n        this,\n        'registryPath',\n        options.registryPath,\n        commandDefaults.registryPath,\n        process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n      ),\n      pidsDir: resolveConfiguredOption(\n        this,\n        'pidsDir',\n        options.pidsDir,\n        commandDefaults.pidsDir,\n        process.env.PLAYWRIGHT_PIDS_DIR,\n      ),\n      profilesDir: resolveConfiguredOption(\n        this,\n        'profilesDir',\n        options.profilesDir,\n        commandDefaults.profilesDir,\n        process.env.PLAYWRIGHT_PROFILES_DIR,\n      ),\n      snippetsDir: resolveConfiguredOption(\n        this,\n        'snippetsDir',\n        options.snippetsDir,\n        commandDefaults.snippetsDir,\n        process.env.BROWSE_TOOL_SNIPPETS_DIR,\n      ),\n      proxyConfigDir: resolveConfiguredOption(\n        this,\n        'proxyConfigDir',\n        options.proxyConfigDir,\n        commandDefaults.proxyConfigDir,\n        process.env[PROXY_CONFIG_DIR_ENV_VAR],\n      ),\n      workspaceRoot: resolveConfiguredOption(\n        this,\n        'workspaceRoot',\n        options.workspaceRoot,\n        commandDefaults.workspaceRoot,\n        process.env[BROWSE_TOOL_WORKSPACE_ROOT_ENV],\n      ),\n    };\n    let processLease: ProcessLease | undefined;\n\n    try {\n      const requestedPort = Number.parseInt(resolvedOptions.port, 10);\n      const serviceName = 'browse-tool-http';\n      const environment = process.env.NODE_ENV || 'development';\n      const repositoryPath = setWorkspaceRootEnv(\n        resolveWorkspaceRoot({\n          startPath: process.cwd(),\n          env: {\n            ...process.env,\n            [BROWSE_TOOL_WORKSPACE_ROOT_ENV]: resolvedOptions.workspaceRoot,\n          },\n        }),\n      );\n\n      // Set environment variables from CLI options\n      const registryPath =\n        resolvedOptions.registryPath || resolvedOptions.registryDir || process.env.PLAYWRIGHT_REGISTRY_PATH;\n      if (registryPath) {\n        process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n        process.env.PORT_REGISTRY_PATH = registryPath;\n        process.env.PROCESS_REGISTRY_PATH = resolveSiblingRegistryPath(registryPath, 'processes.json')!;\n      }\n      process.env.PLAYWRIGHT_HOST = resolvedOptions.host;\n      process.env.PLAYWRIGHT_PORT = resolvedOptions.port;\n      process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n\n      if (resolvedOptions.pidsDir) {\n        process.env.PLAYWRIGHT_PIDS_DIR = resolvedOptions.pidsDir;\n      }\n      if (resolvedOptions.profilesDir) {\n        process.env.PLAYWRIGHT_PROFILES_DIR = resolvedOptions.profilesDir;\n      }\n      if (resolvedOptions.snippetsDir) {\n        process.env.BROWSE_TOOL_SNIPPETS_DIR = path.resolve(resolvedOptions.snippetsDir);\n      }\n      if (resolvedOptions.proxyConfigDir) {\n        process.env[PROXY_CONFIG_DIR_ENV_VAR] = path.resolve(resolvedOptions.proxyConfigDir);\n      }\n\n      console.log('Starting HTTP server for browser automation...');\n      console.log(`   Port: ${requestedPort}`);\n      console.log(`   Host: ${resolvedOptions.host}`);\n      console.log(`   Headless: ${resolvedOptions.headless}`);\n      console.log(`   Idle Timeout: ${resolvedOptions.idleTimeout} minutes`);\n      if (process.env.BROWSE_TOOL_SNIPPETS_DIR) {\n        console.log(`   Snippets Dir: ${process.env.BROWSE_TOOL_SNIPPETS_DIR}`);\n      }\n      if (process.env[PROXY_CONFIG_DIR_ENV_VAR]) {\n        console.log(`   Proxy Config Dir: ${process.env[PROXY_CONFIG_DIR_ENV_VAR]}`);\n      }\n\n      // Create InversifyJS container with full HTTP server services\n      const container = createHttpContainer();\n      const processRegistry = container.get<ProcessRegistryService>(PLAYWRIGHT_TYPES.ProcessRegistryService);\n      const browserProcessRegistry = container.get<BrowserProcessRegistry>(PLAYWRIGHT_TYPES.BrowserProcessRegistry);\n\n      // Filled in once the shutdown routine below exists; the endpoint is wired\n      // up front so it is live from the moment the server accepts requests.\n      const shutdownRequest: { invoke?: () => void } = {};\n      const idleShutdownRequest: { invoke?: () => void } = {};\n\n      // Resolved lazily: the port is only settled once the registry hands it over.\n      const binding: { port?: number } = {};\n\n      // Start idle cleanup service\n      const idleCleanupService = container.get<IIdleCleanupService>(PLAYWRIGHT_TYPES.IdleCleanupService);\n      idleCleanupService.start(() => idleShutdownRequest.invoke?.());\n\n      // Create HTTP server\n      const app = createHttpServer(container, {\n        onShutdownRequested: () => shutdownRequest.invoke?.(),\n        onRequestServed: () => idleCleanupService.recordServerActivity(),\n        resolveBinding: () => ({ host: resolvedOptions.host, port: binding.port }),\n      });\n\n      // Setup WebSocket support for extension communication\n      const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });\n      setupWebSocketRoutes(app, container, upgradeWebSocket);\n\n      // Configure WebSocketHub event handlers for extension communication\n      const wsHub = container.get<WebSocketHub>(PLAYWRIGHT_TYPES.WebSocketHub);\n      const taskQueue = container.get<ExtensionTaskQueue>(PLAYWRIGHT_TYPES.ExtensionTaskQueue);\n      const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n      const sessionRegistry = container.get<IExtensionSessionRegistry>(PLAYWRIGHT_TYPES.ExtensionSessionRegistry);\n      const pageRegistry = container.get<import('../services/PageRegistry.js').IPageRegistry>(\n        PLAYWRIGHT_TYPES.PageRegistry,\n      );\n\n      wsHub.setEventHandlers({\n        onSessionRegister: (connection, message) => {\n          if (message.type === 'session:register') {\n            const requestedBrowserId = message.payload.browserId || connection.browserId;\n            const existingBrowser = browserService.getBrowser(requestedBrowserId);\n\n            let browserId: string;\n            let pageId: string;\n\n            if (existingBrowser && (existingBrowser.mode === 'extension' || existingBrowser.mode === 'vm')) {\n              browserId = existingBrowser.id;\n              const existingPages = pageRegistry.findByBrowser(browserId);\n              pageId = existingBrowser.currentPageId || existingPages[0]?.id || '';\n\n              wsHub.reassignConnection(connection.id, browserId);\n\n              if (!pageId) {\n                pageId = pageRegistry.registerExtensionPage(browserId);\n                existingBrowser.pageIds.add(pageId);\n                existingBrowser.currentPageId = pageId;\n              }\n\n              console.log(`[WebSocket] Extension connected to existing browser: ${browserId}, pageId: ${pageId}`);\n            } else {\n              browserId = requestedBrowserId;\n              browserService.registerExtensionBrowserWithId(browserId);\n              pageId = pageRegistry.registerExtensionPage(browserId);\n\n              const browserInstance = browserService.getBrowser(browserId);\n              if (browserInstance) {\n                browserInstance.pageIds.add(pageId);\n                browserInstance.currentPageId = pageId;\n              }\n\n              console.log(`[WebSocket] Extension connected with new browser: ${browserId}, pageId: ${pageId}`);\n            }\n\n            const pageEntry = pageRegistry.get(pageId);\n            if (pageEntry) {\n              pageEntry.extensionTabId = message.payload.tabId;\n              pageEntry.url = message.payload.url ?? pageEntry.url;\n            }\n\n            const session = sessionRegistry.register({\n              browserId,\n              tabId: message.payload.tabId,\n              url: message.payload.url,\n              metadata: {\n                transport: 'websocket',\n              },\n            });\n            wsHub.sendSessionAck(connection, message.id ?? '', session.id, session.controlMode);\n\n            wsHub.broadcastPageCreated(browserId, pageId, pageRegistry.get(pageId)?.url);\n          }\n        },\n        onTaskResult: (_connection, message) => {\n          if (message.type === 'task:result') {\n            const task = taskQueue.submitResult({\n              taskId: message.payload.taskId,\n              success: message.payload.success,\n              result: message.payload.result,\n              error: message.payload.error,\n            });\n\n            if (task?.browserId) {\n              browserService.recordBrowserActivity(task.browserId, task.pageId);\n            }\n          }\n        },\n        onTabMapped: (_connection, message) => {\n          if (message.type === 'tab:mapped') {\n            const pageEntry = pageRegistry.get(message.payload.pageId);\n            if (!pageEntry) {\n              return;\n            }\n\n            pageEntry.extensionTabId = message.payload.tabId;\n            browserService.recordBrowserActivity(pageEntry.browserId, pageEntry.id);\n          }\n        },\n        onDisconnect: (connection) => {\n          if (connection.sessionId) {\n            sessionRegistry.removeSession(connection.sessionId);\n          }\n          console.log(`[WebSocket] Extension disconnected: ${connection.browserId}`);\n\n          // A reconnect leaves another live connection, so only fail the browser's\n          // work once nothing can deliver it. Otherwise callers wait out the full\n          // task timeout for a result that can never arrive.\n          if (!wsHub.hasConnection(connection.browserId)) {\n            taskQueue.failPendingTasksForBrowser(\n              connection.browserId,\n              `Extension for browser \"${connection.browserId}\" disconnected`,\n            );\n          }\n        },\n      });\n\n      // Sockets can die without a close frame (laptop suspend, crashed\n      // extension); this drops them so pushes fall back to HTTP polling.\n      wsHub.startLivenessSweep();\n\n      const portRegistry = new PortRegistryService(process.env.PORT_REGISTRY_PATH);\n\n      if (registryPath) {\n        console.log(`   Registry path: ${registryPath}`);\n      } else {\n        console.log('   Registry path: default (~/.port-registry/ports.json)');\n      }\n\n      const finalPort = await registerSelf({\n        portRegistry,\n        repositoryPath,\n        serviceName,\n        environment,\n        requestedPort,\n        host: resolvedOptions.host,\n        metadata: {\n          healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host, requestedPort)}/health`,\n          headless: resolvedOptions.headless,\n          idleTimeout: resolvedOptions.idleTimeout,\n          version: getPackageVersion(),\n        },\n      });\n\n      binding.port = finalPort;\n\n      // Start server with WebSocket support\n      const server = serve({\n        fetch: app.fetch,\n        port: finalPort,\n        hostname: resolvedOptions.host,\n      });\n\n      // Inject WebSocket handler into the server\n      injectWebSocket(server);\n\n      await browserProcessRegistry.reconcileOrphanedBrowsers({\n        currentServerPid: process.pid,\n        currentPort: finalPort,\n        reconnectGraceMs: 0,\n      });\n\n      // Sweep temp directories left by daemons that were killed before they\n      // could clean up. Fire-and-forget: startup must not wait on disk I/O.\n      void cleanupOrphanedBrowserTempDirs().catch((error) => {\n        console.error('Temp directory cleanup error:', error);\n      });\n\n      // Rows for daemons that died without releasing anything accumulate forever\n      // and later read as live services. A start is the natural point to sweep\n      // them, and like the temp sweep it must not delay serving.\n      void processRegistry.cleanupStaleProcesses().catch((error) => {\n        console.error('Process registry cleanup error:', error);\n      });\n      void portRegistry.cleanupStaleAllocations().catch((error) => {\n        console.error('Port registry cleanup error:', error);\n      });\n\n      // Chrome and its helpers are children of this daemon, so the registry needs\n      // to know it can signal the whole group; releasing the bare pid orphans them.\n      const processIdentity = await resolveSelfProcessIdentity();\n\n      processLease = await createProcessLease(\n        {\n          repositoryPath,\n          serviceName,\n          serviceType: 'service',\n          environment,\n          pid: process.pid,\n          port: finalPort,\n          host: resolvedOptions.host,\n          processStartedAt: processIdentity.processStartedAt,\n          processGroupId: processIdentity.processGroupId,\n          terminationMode: processIdentity.terminationMode,\n          metadata: {\n            healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host, finalPort)}/health`,\n            headless: resolvedOptions.headless,\n            idleTimeout: resolvedOptions.idleTimeout,\n            transport: 'http',\n          },\n        },\n        processRegistry,\n      );\n\n      const baseUrl = buildPlaywrightBaseUrl(resolvedOptions.host, finalPort);\n      console.log(`HTTP server listening on ${baseUrl}`);\n      console.log(`  Health check: ${baseUrl}/health`);\n      console.log(`  Execute tool: POST ${baseUrl}/execute`);\n\n      console.log('\\nPress Ctrl+C to stop the server');\n\n      // Shutdown runs at most once: the first reason wins and later callers\n      // await the same teardown instead of racing a second one.\n      let shutdownPromise: Promise<void> | undefined;\n\n      const doShutdown = async (reason: string, exitCode: number): Promise<void> => {\n        console.log(`\\n\\n${reason} received. Shutting down gracefully...`);\n\n        try {\n          idleCleanupService.stop();\n          wsHub.stopLivenessSweep();\n\n          server.close();\n          console.log('Server closed');\n\n          // Close all browsers\n          try {\n            const browserService = container.get<IBrowserService>(PLAYWRIGHT_TYPES.BrowserService);\n            await browserService.closeAll();\n            console.log('All browsers closed');\n          } catch {\n            // Browser service may not have browsers\n          }\n\n          // Stealth browsers live in their own registry, so close them separately\n          try {\n            const stealthLauncher = container.get<IStealthLauncher>(PLAYWRIGHT_TYPES.StealthLauncher);\n            await stealthLauncher.closeAll();\n            console.log('All stealth browsers closed');\n          } catch {\n            // StealthLauncher may not be bound or may have no browsers\n          }\n\n          // Dev servers started for specs are children of this process, and its\n          // 'exit' handler cannot wait on them, so stop them while we still can.\n          try {\n            const webServerManager = container.get<IWebServerManager>(PLAYWRIGHT_TYPES.WebServerManager);\n            await webServerManager.stopServer();\n            console.log('Dev server stopped');\n          } catch (webServerError) {\n            console.error('Dev server cleanup error:', webServerError);\n          }\n\n          // Deregister service\n          try {\n            if (processLease) {\n              await processLease.release({ kill: false });\n            }\n            console.log('Service deregistered');\n          } catch (processReleaseError) {\n            console.error('Process registry cleanup error:', processReleaseError);\n          }\n\n          // Release our port row. Without this a clean exit leaves a row behind\n          // pointing at a dead pid, which later looks like a stale daemon.\n          try {\n            await portRegistry.releasePorts({\n              repositoryPath,\n              serviceName,\n              serviceType: 'tool',\n              environment,\n              pid: process.pid,\n            });\n          } catch (portReleaseError) {\n            console.error('Port registry cleanup error:', portReleaseError);\n          }\n\n          console.log('Goodbye!');\n          process.exit(exitCode);\n        } catch (error) {\n          console.error('Error during shutdown:', error);\n          process.exit(1);\n        }\n      };\n\n      const shutdown = (reason: string, exitCode = 0): Promise<void> => {\n        shutdownPromise ??= doShutdown(reason, exitCode);\n        return shutdownPromise;\n      };\n\n      shutdownRequest.invoke = () => void shutdown('Shutdown request', 0);\n      idleShutdownRequest.invoke = () => void shutdown('Server idle timeout', 0);\n\n      // A bind failure arrives as an 'error' event; without a handler it becomes\n      // an uncaught exception and the daemon dies still holding its registry row.\n      server.on('error', (error: NodeJS.ErrnoException) => {\n        if (error.code === 'EADDRINUSE') {\n          console.error(`PORT_IN_USE: port ${finalPort} is already bound by another process`);\n        } else {\n          console.error('HTTP server error:', error);\n        }\n        void shutdown('Server error', 1);\n      });\n\n      // Register signal handlers\n      process.on('SIGINT', () => void shutdown('SIGINT'));\n      process.on('SIGTERM', () => void shutdown('SIGTERM'));\n      process.on('SIGHUP', () => void shutdown('SIGHUP'));\n\n      process.on('uncaughtException', (error) => {\n        console.error('Uncaught exception:', error);\n        void shutdown('Uncaught exception', 1);\n      });\n      process.on('unhandledRejection', (reason) => {\n        console.error('Unhandled rejection:', reason);\n        void shutdown('Unhandled rejection', 1);\n      });\n\n      // Keep the detached child process alive even when no TTY/stdin is attached.\n      // Without this, the spawned bootstrap process can exit before health checks run.\n      await new Promise<never>(() => {});\n    } catch (error) {\n      if (processLease) {\n        try {\n          await processLease.release({ kill: false });\n        } catch (cleanupError) {\n          console.error('Process registry cleanup error:', cleanupError);\n        }\n      }\n      console.error('Error starting HTTP server:', error);\n      process.exit(1);\n    }\n  });\n","/**\n * Tool tag definitions for filtering MCP tools by category.\n *\n * DESIGN PATTERNS:\n * - Centralized tag registry pattern\n * - Maps tool names to tag arrays for proxy-level filtering\n * - Tags are additive — a tool can belong to multiple categories\n *\n * CODING STANDARDS:\n * - Keep tag names lowercase, single-word where possible\n * - Keep map in sync with tool registrations in container\n * - Add new tools here when creating them\n *\n * AVOID:\n * - Defining tags in individual tool files (single source of truth)\n * - Using tags not listed in AVAILABLE_TAGS\n */\n\n/** All recognized tag values */\nexport const AVAILABLE_TAGS = [\n  'input',\n  'navigation',\n  'snapshot',\n  'page',\n  'dialog',\n  'network',\n  'console',\n  'script',\n  'emulation',\n  'testing',\n  'tracing',\n  'profile',\n  'browser',\n  'spec',\n  'code',\n] as const;\n\nexport type ToolTag = (typeof AVAILABLE_TAGS)[number];\n\n/** Maps each tool name to its tags */\nexport const TOOL_TAGS: Record<string, ToolTag[]> = {\n  // Input tools\n  browser_click: ['input'],\n  browser_fill: ['input'],\n  browser_type: ['input'],\n  browser_select: ['input'],\n  browser_hover: ['input'],\n  browser_drag: ['input'],\n  browser_press_key: ['input'],\n  browser_upload_file: ['input'],\n  // Navigation tools\n  browser_navigate: ['navigation'],\n  browser_go_back: ['navigation'],\n  browser_go_forward: ['navigation'],\n  browser_reload: ['navigation'],\n  browser_wait_for: ['navigation'],\n  // Snapshot tools\n  browser_snapshot: ['snapshot'],\n  browser_screenshot: ['snapshot'],\n  browser_pdf: ['snapshot'],\n  // Page management tools\n  browser_list_pages: ['page'],\n  browser_new_page: ['page'],\n  browser_select_page: ['page'],\n  browser_close_page: ['page'],\n  browser_resize_page: ['page', 'emulation'],\n  // Dialog tools\n  browser_handle_dialog: ['dialog'],\n  // Network tools\n  browser_list_network_requests: ['network'],\n  browser_get_network_request: ['network'],\n  // Console tools\n  browser_list_console_messages: ['console'],\n  // Script tools\n  browser_evaluate_script: ['script'],\n  // Emulation tools\n  browser_emulate: ['emulation'],\n  // Testing and tracing tools\n  browser_expect: ['testing'],\n  browser_start_trace: ['tracing'],\n  browser_stop_trace: ['tracing'],\n  // Profile management tools\n  browser_list_profiles: ['profile'],\n  browser_delete_profile: ['profile'],\n  // Spec tools\n  run_spec: ['spec', 'testing'],\n  discover_specs: ['spec', 'testing'],\n  // Browser lifecycle tools\n  browser_launch: ['browser'],\n  browser_close: ['browser'],\n  // Code execution tools\n  browser_run_code: ['code', 'script'],\n};\n\n/**\n * Resolves a set of tag names to matching tool names.\n * @param tags - Tags to include\n * @returns Set of tool names that match any of the given tags\n */\nexport function getToolNamesByTags(tags: string[]): Set<string> {\n  const result = new Set<string>();\n  for (const [toolName, toolTags] of Object.entries(TOOL_TAGS)) {\n    if (toolTags.some((t) => tags.includes(t))) {\n      result.add(toolName);\n    }\n  }\n  return result;\n}\n","/**\n * Proxy MCP Server\n *\n * DESIGN PATTERNS:\n * - Proxy pattern for forwarding requests to HTTP server\n * - MCP Server setup pattern with request handlers\n * - Session tracking for cleanup on shutdown\n *\n * CODING STANDARDS:\n * - Create a low-level server instance using @modelcontextprotocol/server\n * - Use proper error handling in request handlers\n * - Export factory function for server creation\n * - Track browsers/pages for session cleanup\n *\n * AVOID:\n * - Business logic in handlers\n * - Hardcoded URLs or configuration\n */\n\nimport { type CallToolResult, Server } from '@modelcontextprotocol/server';\nimport { getToolNamesByTags } from '../constants/tool-tags.js';\nimport type { IMcpSessionTracker } from '../services/McpSessionTracker.js';\nimport { type ProxyInput, resolveProxyConfig } from '../services/ProxyConfigService.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { toMcpListTool } from '../utils/mcpToolDefinition.js';\nimport { OWNER_HEADER } from '../utils/ownerContext.js';\nimport { resolveTelemetryContextFromEnv, telemetryContextToHeaders } from '../utils/telemetryContext.js';\n\n/** Browser launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/**\n * Tool filtering configuration for proxy server.\n * When tags are specified, only tools matching those tags are included.\n * Excluded tools are then removed from the result.\n */\nexport interface ToolFilterConfig {\n  /** Include only tools matching these tags */\n  tags?: string[];\n  /** Exclude specific tools by name (applied after tag filtering) */\n  exclude?: string[];\n}\n\n/**\n * Configuration for the proxy server\n */\nexport interface ProxyServerConfig {\n  /** Base URL of the HTTP server to proxy requests to */\n  httpBaseUrl: string;\n  /** Session tracker for tracking browsers/pages created in this session */\n  sessionTracker?: IMcpSessionTracker;\n  /** Default launch mode for browser_launch tool when not specified */\n  defaultMode?: LaunchMode;\n  /** Tool filtering options */\n  toolFilter?: ToolFilterConfig;\n  /** Optional directory containing custom tool scripts */\n  customToolsDir?: string;\n  /** Pre-selected profile enforced for this MCP session */\n  enforcedProfileName?: string;\n  /** Directory containing default.yaml, session.yaml, and profile proxy config files */\n  proxyConfigDir?: string;\n  /** Stable application owner, propagated to the browser HTTP runtime for page ownership. */\n  ownerId?: string;\n  /** HTTP: list tools without an owner, but reject tool calls until an owner is supplied. */\n  requireOwnerForToolCalls?: boolean;\n}\n\n/**\n * Response from the /tools endpoint\n */\ninterface ToolsResponse {\n  tools: ToolDefinition[];\n  error?: string;\n}\n\ninterface CustomToolDescriptor {\n  name: string;\n  description: string;\n  suggestionActions?: string;\n  inputSchema: ToolDefinition['inputSchema'];\n  capabilities?: Record<string, unknown>;\n}\n\ninterface CustomToolsResponse {\n  tools: CustomToolDescriptor[];\n  error?: string;\n}\n\n/**\n * Response from the /execute endpoint\n */\ninterface ExecuteResponse {\n  success: boolean;\n  result?: CallToolResult;\n  error?: string;\n}\n\ninterface ProfileListResponse {\n  profileCount?: number;\n  profiles?: Array<{\n    name?: string;\n    browserType?: string;\n    viewport?: unknown;\n    userAgent?: string | null;\n    locale?: string | null;\n    timezone?: string | null;\n    colorScheme?: string | null;\n    createdAt?: string;\n    updatedAt?: string;\n  }>;\n}\n\nconst TOOLS_REQUEST_TIMEOUT_MS = 3000;\nconst TOOLS_REQUEST_MAX_ATTEMPTS = 4;\nconst TOOLS_REQUEST_RETRY_BASE_DELAY_MS = 150;\n\n/**\n * Creates a proxy MCP server that forwards all requests to an HTTP server.\n *\n * This server acts as a thin proxy layer:\n * - ListTools: Fetches tool definitions from HTTP server's /tools endpoint\n * - CallTool: Forwards tool execution to HTTP server's /execute endpoint\n *\n * @param config - Proxy server configuration\n * @returns Configured MCP Server instance\n */\n/** Tools that create browsers */\nconst BROWSER_CREATING_TOOLS = ['browser_launch'];\n/** Tools that create pages */\nconst PAGE_CREATING_TOOLS = ['browser_new_page'];\n/** Tools that close browsers */\nconst BROWSER_CLOSING_TOOLS = ['browser_close'];\n/** Tools that close pages */\nconst PAGE_CLOSING_TOOLS = ['browser_close_page'];\n\nfunction resolveToolNameAlias(toolName: string, hasSessionTracker: boolean): string {\n  const normalizedName = toolName.replaceAll('-', '_');\n\n  if (\n    hasSessionTracker &&\n    (normalizedName === 'browser_list' ||\n      normalizedName === 'browser_list_session' ||\n      normalizedName === 'browser_list_sessions')\n  ) {\n    return 'browser_list_session';\n  }\n\n  if (toolName.startsWith('browser-') || toolName === 'run-spec' || toolName === 'discover-specs') {\n    return normalizedName;\n  }\n\n  return toolName;\n}\n\n/**\n * Parse browser/page info from tool result\n */\nfunction parseResourceFromResult(result: ExecuteResponse['result']): {\n  browserId?: string;\n  pageId?: string;\n  mode?: string;\n  url?: string;\n  closedBrowserId?: string;\n  closedPageId?: string;\n  browserKeptOpen?: boolean;\n} | null {\n  const first = result?.content?.[0];\n  if (first?.type !== 'text') {\n    return null;\n  }\n\n  try {\n    const parsed = JSON.parse(first.text);\n    return {\n      browserId: parsed.browserId,\n      pageId: parsed.pageId,\n      mode: parsed.mode,\n      url: parsed.url,\n      closedBrowserId: parsed.closedBrowserId,\n      closedPageId: parsed.closedPageId,\n      browserKeptOpen: parsed.browserKeptOpen,\n    };\n  } catch {\n    return null;\n  }\n}\n\nasync function wait(delayMs: number): Promise<void> {\n  await new Promise((resolve) => setTimeout(resolve, delayMs));\n}\n\nasync function fetchToolsFromHttpServer(httpBaseUrl: string): Promise<ToolDefinition[]> {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n  try {\n    const response = await fetch(`${httpBaseUrl}/tools`, { signal: controller.signal });\n\n    if (!response.ok) {\n      throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n    }\n\n    const data = (await response.json()) as ToolsResponse;\n    if (data.error) {\n      throw new Error(data.error);\n    }\n\n    if (!Array.isArray(data.tools)) {\n      throw new Error('Invalid /tools response: missing tools array');\n    }\n\n    if (data.tools.length === 0) {\n      throw new Error('HTTP server returned an empty tool list');\n    }\n\n    return data.tools;\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\nfunction buildCustomToolsUrl(httpBaseUrl: string, customToolsDir: string): string {\n  const url = new URL('/custom-tools', httpBaseUrl);\n  url.searchParams.set('dir', customToolsDir);\n  return url.toString();\n}\n\nasync function fetchCustomToolsFromHttpServer(\n  httpBaseUrl: string,\n  customToolsDir: string,\n): Promise<CustomToolDescriptor[]> {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), TOOLS_REQUEST_TIMEOUT_MS);\n\n  try {\n    const response = await fetch(buildCustomToolsUrl(httpBaseUrl, customToolsDir), { signal: controller.signal });\n\n    if (!response.ok) {\n      throw new Error(`HTTP error ${response.status}: ${response.statusText}`);\n    }\n\n    const data = (await response.json()) as CustomToolsResponse;\n    if (data.error) {\n      throw new Error(data.error);\n    }\n\n    if (!Array.isArray(data.tools)) {\n      throw new Error('Invalid /custom-tools response: missing tools array');\n    }\n\n    return data.tools;\n  } finally {\n    clearTimeout(timeoutId);\n  }\n}\n\nasync function fetchToolsWithRetry(httpBaseUrl: string): Promise<ToolDefinition[]> {\n  let lastError: Error | undefined;\n\n  for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n    try {\n      return await fetchToolsFromHttpServer(httpBaseUrl);\n    } catch (error) {\n      lastError = error instanceof Error ? error : new Error(String(error));\n      if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n        await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n      }\n    }\n  }\n\n  throw new Error(\n    `Failed to fetch tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n    { cause: lastError },\n  );\n}\n\nasync function fetchCustomToolsWithRetry(httpBaseUrl: string, customToolsDir: string): Promise<CustomToolDescriptor[]> {\n  let lastError: Error | undefined;\n\n  for (let attempt = 1; attempt <= TOOLS_REQUEST_MAX_ATTEMPTS; attempt += 1) {\n    try {\n      return await fetchCustomToolsFromHttpServer(httpBaseUrl, customToolsDir);\n    } catch (error) {\n      lastError = error instanceof Error ? error : new Error(String(error));\n      if (attempt < TOOLS_REQUEST_MAX_ATTEMPTS) {\n        await wait(TOOLS_REQUEST_RETRY_BASE_DELAY_MS * attempt);\n      }\n    }\n  }\n\n  throw new Error(\n    `Failed to fetch custom tools after ${TOOLS_REQUEST_MAX_ATTEMPTS} attempts: ${lastError?.message ?? 'Unknown error'}`,\n    { cause: lastError },\n  );\n}\n\n/**\n * Build the set of allowed tool names based on filter config.\n * Returns null if no filtering is configured (all tools allowed).\n */\nfunction buildAllowedToolNames(filter?: ToolFilterConfig): Set<string> | null {\n  if (!filter?.tags?.length && !filter?.exclude?.length) {\n    return null;\n  }\n\n  const taggedNames = filter.tags?.length ? getToolNamesByTags(filter.tags) : null;\n  const excludeSet = new Set(filter.exclude ?? []);\n\n  if (!taggedNames) {\n    // Only exclude, no tag filter — return null to allow all, exclusion applied separately\n    return excludeSet.size > 0 ? excludeSet : null;\n  }\n\n  // Remove excluded tools from the tag-matched set\n  for (const name of excludeSet) {\n    taggedNames.delete(name);\n  }\n\n  return taggedNames;\n}\n\n/**\n * Build the set of input-schema keys that are fixed by launch args, keyed by\n * tool name. When a launch arg pins a value, its matching schema key is removed\n * from the exposed input schema so the agent is not asked to supply something the\n * proxy already controls at call time.\n */\nfunction buildFixedSchemaKeys(\n  config: Pick<ProxyServerConfig, 'defaultMode' | 'enforcedProfileName'>,\n): Map<string, Set<string>> {\n  const fixedKeysByTool = new Map<string, Set<string>>();\n  const addFixedKey = (toolName: string, key: string): void => {\n    const keys = fixedKeysByTool.get(toolName) ?? new Set<string>();\n    keys.add(key);\n    fixedKeysByTool.set(toolName, keys);\n  };\n\n  // --mode fixes browser_launch's execution mode (applied as a default in CallTool).\n  if (config.defaultMode) {\n    addFixedKey('browser_launch', 'mode');\n  }\n  // --profile / X-Profile header pins browser_launch to one profile (force-applied in CallTool).\n  if (config.enforcedProfileName) {\n    addFixedKey('browser_launch', 'profileName');\n  }\n\n  return fixedKeysByTool;\n}\n\n/**\n * Return a copy of the tool definition with launch-arg-fixed keys removed from\n * its input schema. Clones the affected schema so cached definitions stay intact.\n */\nfunction excludeFixedSchemaKeys(tool: ToolDefinition, fixedKeysByTool: Map<string, Set<string>>): ToolDefinition {\n  const fixedKeys = fixedKeysByTool.get(tool.name);\n  if (!fixedKeys?.size) {\n    return tool;\n  }\n\n  const schema = tool.inputSchema as {\n    properties?: Record<string, unknown>;\n    required?: unknown;\n    [key: string]: unknown;\n  };\n  if (!schema.properties || typeof schema.properties !== 'object') {\n    return tool;\n  }\n\n  const nextProperties = Object.fromEntries(Object.entries(schema.properties).filter(([key]) => !fixedKeys.has(key)));\n  const nextSchema: Record<string, unknown> = { ...schema, properties: nextProperties };\n  if (Array.isArray(schema.required)) {\n    nextSchema.required = schema.required.filter((name) => typeof name !== 'string' || !fixedKeys.has(name));\n  }\n\n  return { ...tool, inputSchema: nextSchema };\n}\n\nexport function createProxyServer(config: ProxyServerConfig): Server {\n  const {\n    httpBaseUrl,\n    sessionTracker,\n    defaultMode,\n    toolFilter,\n    customToolsDir,\n    enforcedProfileName,\n    proxyConfigDir,\n    ownerId,\n    requireOwnerForToolCalls,\n  } = config;\n\n  // This proxy runs inside the agent's process tree, so its env carries the\n  // agent identity. Send it on every call so the shared daemon attributes its\n  // logs to this agent rather than to whichever agent spawned it.\n  const telemetryHeaders = telemetryContextToHeaders(resolveTelemetryContextFromEnv());\n\n  // Pre-compute allowed tool names for filtering\n  const allowedToolNames = toolFilter?.tags?.length ? buildAllowedToolNames(toolFilter) : null;\n  const excludedToolNames = new Set(toolFilter?.exclude ?? []);\n  const hasFilter = allowedToolNames !== null || excludedToolNames.size > 0;\n\n  // Schema keys fixed by launch args, removed from the agent-facing tool schemas.\n  const fixedSchemaKeys = buildFixedSchemaKeys({ defaultMode, enforcedProfileName });\n\n  const server = new Server(\n    {\n      name: 'browse-tool',\n      version: '0.1.0',\n    },\n    {\n      capabilities: {\n        tools: {},\n      },\n    },\n  );\n\n  // Session-specific tool definition\n  const sessionToolDefinition: ToolDefinition = {\n    name: 'browser_list_session',\n    description:\n      'List all browsers and pages created in this MCP session. Use this to see what resources you have available.',\n    inputSchema: {\n      type: 'object',\n      properties: {},\n      required: [],\n      additionalProperties: false,\n    },\n  };\n\n  let cachedTools: ToolDefinition[] = [];\n  let cachedCustomTools: CustomToolDescriptor[] = [];\n  let cachedBuiltinToolNames: Set<string> = new Set();\n\n  /**\n   * Apply tool filtering based on --tags and --exclude options.\n   * When tags are specified, only tools matching those tags are included.\n   * Excluded tools are then removed regardless.\n   */\n  function filterTools(tools: ToolDefinition[]): ToolDefinition[] {\n    if (!hasFilter) {\n      return tools;\n    }\n\n    return tools.filter((tool) => {\n      if (excludedToolNames.has(tool.name)) {\n        return false;\n      }\n      if (allowedToolNames !== null) {\n        return allowedToolNames.has(tool.name);\n      }\n      return true;\n    });\n  }\n\n  function filterCustomTools(tools: CustomToolDescriptor[]): CustomToolDescriptor[] {\n    return tools.filter((tool) => !excludedToolNames.has(tool.name));\n  }\n\n  function applyEnforcedProfileToLaunchArgs(args: Record<string, unknown>): Record<string, unknown> {\n    if (!enforcedProfileName) {\n      return args;\n    }\n\n    return { ...args, profileName: enforcedProfileName };\n  }\n\n  function applyResolvedProxyToLaunchArgs(args: Record<string, unknown>): Record<string, unknown> {\n    const proxy = resolveProxyConfig({\n      proxy: args.proxy as ProxyInput,\n      profileName: typeof args.profileName === 'string' ? args.profileName : undefined,\n      proxyConfigDir,\n    });\n\n    if (!proxy) {\n      const argsWithoutProxy = { ...args };\n      delete argsWithoutProxy.proxy;\n      return argsWithoutProxy;\n    }\n\n    return { ...args, proxy };\n  }\n\n  function rewriteProfileListResult(\n    result: NonNullable<ExecuteResponse['result']>,\n  ): NonNullable<ExecuteResponse['result']> {\n    if (!enforcedProfileName) {\n      return result;\n    }\n\n    const first = result.content[0];\n    if (first?.type !== 'text') {\n      return result;\n    }\n    const text = first.text;\n\n    try {\n      const parsed = JSON.parse(text) as ProfileListResponse;\n      const profiles = Array.isArray(parsed.profiles) ? parsed.profiles : [];\n      const filteredProfiles = profiles.filter((profile) => profile?.name === enforcedProfileName);\n\n      return {\n        ...result,\n        content: [\n          {\n            ...first,\n            text: JSON.stringify(\n              {\n                ...parsed,\n                profileCount: filteredProfiles.length,\n                profiles: filteredProfiles,\n              },\n              null,\n              2,\n            ),\n          },\n          ...result.content.slice(1),\n        ],\n      };\n    } catch {\n      return result;\n    }\n  }\n\n  function toMcpToolDefinition(tool: CustomToolDescriptor): ToolDefinition {\n    return {\n      name: tool.name,\n      description: tool.description,\n      inputSchema: tool.inputSchema,\n      annotations: tool.capabilities,\n    };\n  }\n\n  /**\n   * Check if a tool name is allowed by the current filter.\n   */\n  function isToolAllowed(toolName: string): boolean {\n    if (excludedToolNames.has(toolName)) {\n      return false;\n    }\n    if (allowedToolNames !== null) {\n      return allowedToolNames.has(toolName);\n    }\n    return true;\n  }\n\n  /**\n   * ListTools handler - fetches tool definitions from HTTP server + session tool\n   */\n  server.setRequestHandler('tools/list', async () => {\n    try {\n      const toolsFromServer = await fetchToolsWithRetry(httpBaseUrl);\n      cachedTools = toolsFromServer;\n      cachedBuiltinToolNames = new Set(toolsFromServer.map((t) => t.name));\n      const customTools = customToolsDir ? await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir) : [];\n      cachedCustomTools = customTools;\n\n      // Filter tools based on --tags and --exclude, then drop launch-arg-fixed keys.\n      const tools = [\n        ...filterTools(toolsFromServer).map((tool) => excludeFixedSchemaKeys(tool, fixedSchemaKeys)),\n        ...filterCustomTools(customTools).map((tool) => toMcpToolDefinition(tool)),\n      ];\n\n      // Add session-specific tool if tracker is available\n      if (sessionTracker) {\n        tools.push(sessionToolDefinition);\n      }\n\n      return { tools: tools.map(toMcpListTool) };\n    } catch (error) {\n      if (cachedTools.length > 0 || cachedCustomTools.length > 0) {\n        console.error(\n          'Failed to refresh tools from HTTP server. Returning cached tools:',\n          error instanceof Error ? error.message : String(error),\n        );\n\n        const tools = [\n          ...filterTools(cachedTools).map((tool) => excludeFixedSchemaKeys(tool, fixedSchemaKeys)),\n          ...filterCustomTools(cachedCustomTools).map((tool) => toMcpToolDefinition(tool)),\n        ];\n        if (sessionTracker) {\n          tools.push(sessionToolDefinition);\n        }\n        return { tools: tools.map(toMcpListTool) };\n      }\n\n      const message = error instanceof Error ? error.message : String(error);\n      throw new Error(`Failed to list tools from HTTP server at ${httpBaseUrl}: ${message}`, { cause: error });\n    }\n  });\n\n  /**\n   * CallTool handler - forwards tool execution to HTTP server\n   */\n  server.setRequestHandler('tools/call', async (request) => {\n    if (requireOwnerForToolCalls && !ownerId) {\n      return {\n        content: [\n          { type: 'text' as const, text: `Stateful browser tools require the ${OWNER_HEADER} request header.` },\n        ],\n        isError: true,\n      };\n    }\n\n    const { name: requestedName, arguments: args } = request.params;\n    const name = resolveToolNameAlias(requestedName, Boolean(sessionTracker));\n    let customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n\n    // Reject filtered-out tools\n    if (name !== 'browser_list_session' && !isToolAllowed(name)) {\n      return {\n        content: [{ type: 'text', text: `Tool \"${name}\" is not available (filtered by --tags or --exclude)` }],\n        isError: true,\n      };\n    }\n\n    // Handle session-specific tool locally\n    if (name === 'browser_list_session' && sessionTracker) {\n      const state = sessionTracker.getSessionState();\n      return {\n        content: [\n          {\n            type: 'text',\n            text: JSON.stringify(state, null, 2),\n          },\n        ],\n      };\n    }\n\n    // Prepare arguments, injecting defaults where needed\n    let finalArgs = (args || {}) as Record<string, unknown>;\n\n    // Apply the configured launch mode only as a default so callers can still\n    // request a specific runtime, such as VM mode for an isolated session.\n    if (name === 'browser_launch' && defaultMode && finalArgs.mode === undefined) {\n      finalArgs = { ...finalArgs, mode: defaultMode };\n    }\n    if (name === 'browser_launch') {\n      finalArgs = applyEnforcedProfileToLaunchArgs(finalArgs);\n      finalArgs = applyResolvedProxyToLaunchArgs(finalArgs);\n    }\n\n    try {\n      // Only re-fetch custom tools when the tool name is not a known built-in\n      // and not already cached as a custom tool (i.e. it might be a newly-added custom tool).\n      // Fall back to cached custom tools on fetch failure to avoid blocking built-in tool calls.\n      if (customToolsDir && !customToolNames.has(name) && !cachedBuiltinToolNames.has(name)) {\n        try {\n          cachedCustomTools = await fetchCustomToolsWithRetry(httpBaseUrl, customToolsDir);\n          customToolNames = new Set(cachedCustomTools.map((tool) => tool.name));\n        } catch (refreshError) {\n          console.error(\n            `Failed to refresh custom tools, using cached list: ${refreshError instanceof Error ? refreshError.message : String(refreshError)}`,\n          );\n        }\n      }\n\n      const isCustomTool = customToolNames.has(name);\n      const response = await fetch(\n        isCustomTool && customToolsDir ? buildCustomToolsUrl(httpBaseUrl, customToolsDir) : `${httpBaseUrl}/execute`,\n        {\n          method: 'POST',\n          headers: ownerId\n            ? { 'Content-Type': 'application/json', ...telemetryHeaders, [OWNER_HEADER]: ownerId }\n            : { 'Content-Type': 'application/json', ...telemetryHeaders },\n          body: JSON.stringify({ tool: name, arguments: finalArgs }),\n        },\n      );\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        return {\n          content: [{ type: 'text', text: `HTTP error ${response.status}: ${errorText}` }],\n          isError: true,\n        };\n      }\n\n      const data = (await response.json()) as ExecuteResponse;\n\n      if (!data.success) {\n        const first = data.result?.content?.[0];\n        const errorText =\n          data.error || (first?.type === 'text' ? first.text : undefined) || 'Unknown error from HTTP server';\n        return {\n          content: [{ type: 'text', text: errorText }],\n          isError: true,\n        };\n      }\n\n      // Track browsers/pages created in this session\n      if (sessionTracker && data.result) {\n        if (BROWSER_CREATING_TOOLS.includes(name)) {\n          const resource = parseResourceFromResult(data.result);\n          if (resource?.browserId) {\n            sessionTracker.trackBrowser(\n              resource.browserId,\n              (resource.mode as 'playwright' | 'extension' | 'vm' | 'stealth') || 'playwright',\n            );\n            if (resource.pageId) {\n              sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n            }\n          }\n        } else if (PAGE_CREATING_TOOLS.includes(name)) {\n          const resource = parseResourceFromResult(data.result);\n          if (resource?.pageId && resource?.browserId) {\n            sessionTracker.trackPage(resource.pageId, resource.browserId, resource.url);\n          }\n        } else if (BROWSER_CLOSING_TOOLS.includes(name)) {\n          const resource = parseResourceFromResult(data.result);\n          if (resource?.closedBrowserId) {\n            sessionTracker.untrackBrowser(resource.closedBrowserId);\n          }\n        } else if (PAGE_CLOSING_TOOLS.includes(name)) {\n          const resource = parseResourceFromResult(data.result);\n          if (resource?.closedPageId) {\n            sessionTracker.untrackPage(resource.closedPageId);\n          }\n          if (resource?.browserId && resource.browserKeptOpen === false) {\n            sessionTracker.untrackBrowser(resource.browserId);\n          }\n        }\n      }\n\n      if (name === 'browser_list_profiles' && data.result) {\n        return rewriteProfileListResult(data.result);\n      }\n\n      return data.result || { content: [{ type: 'text', text: 'No result returned' }] };\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : String(error);\n      return {\n        content: [{ type: 'text', text: `Failed to execute tool via HTTP server: ${errorMessage}` }],\n        isError: true,\n      };\n    }\n  });\n\n  return server;\n}\n","import { McpSessionTracker } from './McpSessionTracker.js';\n\nconst DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000;\nconst DEFAULT_SWEEP_INTERVAL_MS = 5 * 60 * 1000;\nconst MAX_OWNERS = 512;\n\ninterface OwnerTracker {\n  tracker: McpSessionTracker;\n  ownerId: string;\n  lastActivity: number;\n  activeRequests: number;\n}\n\n/** Application-owned browser state. Modern MCP HTTP has requests, not sessions. */\nexport class McpOwnerTrackerRegistry {\n  private readonly owners = new Map<string, OwnerTracker>();\n  private readonly timer: ReturnType<typeof setInterval>;\n\n  constructor(\n    private readonly cleanup: (tracker: McpSessionTracker, ownerId: string) => Promise<void>,\n    private readonly idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,\n  ) {\n    this.timer = setInterval(\n      () => {\n        void this.expireIdle().catch((error: unknown) => console.error('Failed to expire browser owner:', error));\n      },\n      Math.min(DEFAULT_SWEEP_INTERVAL_MS, idleTimeoutMs),\n    );\n    this.timer.unref();\n  }\n\n  private key(ownerId: string, profileName: string | undefined): string {\n    return JSON.stringify([ownerId, profileName ?? '']);\n  }\n\n  get(ownerId: string, profileName?: string): McpSessionTracker {\n    const key = this.key(ownerId, profileName);\n    let entry = this.owners.get(key);\n    if (!entry) {\n      if (this.owners.size >= MAX_OWNERS) {\n        throw new Error('Too many browser owners; close idle browsers before opening another session');\n      }\n      const tracker = new McpSessionTracker();\n      entry = {\n        tracker,\n        ownerId,\n        lastActivity: Date.now(),\n        activeRequests: 0,\n      };\n      this.owners.set(key, entry);\n    }\n    entry.lastActivity = Date.now();\n    return entry.tracker;\n  }\n\n  beginRequest(ownerId: string | undefined, profileName?: string): () => void {\n    if (!ownerId) return () => undefined;\n    this.get(ownerId, profileName);\n    const record = this.owners.get(this.key(ownerId, profileName));\n    if (!record) return () => undefined;\n    record.activeRequests++;\n    let released = false;\n    return () => {\n      if (released) return;\n      released = true;\n      record.activeRequests--;\n      record.lastActivity = Date.now();\n    };\n  }\n\n  private async closeOwner(key: string): Promise<void> {\n    const entry = this.owners.get(key);\n    if (!entry || entry.activeRequests > 0) return;\n    this.owners.delete(key);\n    try {\n      if (entry.tracker.getSessionState().totalBrowsers > 0) {\n        await this.cleanup(entry.tracker, entry.ownerId);\n      }\n    } catch (error) {\n      // Keep failed cleanup discoverable and retry it on the next sweep.\n      this.owners.set(key, entry);\n      throw error;\n    }\n  }\n\n  async expireIdle(now = Date.now()): Promise<void> {\n    for (const [key, entry] of this.owners) {\n      if (entry.activeRequests === 0 && now - entry.lastActivity >= this.idleTimeoutMs) {\n        await this.closeOwner(key);\n      }\n    }\n  }\n\n  async close(): Promise<void> {\n    clearInterval(this.timer);\n    await Promise.all([...this.owners.keys()].map(async (key) => this.closeOwner(key)));\n  }\n}\n","/**\n * MCP Serve Command\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Transport abstraction pattern for flexible deployment (stdio, HTTP, SSE)\n * - Factory pattern for creating transport handlers\n * - Graceful shutdown pattern with signal handling\n *\n * CODING STANDARDS:\n * - Use async/await for asynchronous operations\n * - Implement proper error handling with try-catch blocks\n * - Handle process signals for graceful shutdown\n * - Provide clear CLI options and help messages\n *\n * AVOID:\n * - Hardcoded configuration values (use CLI options or environment variables)\n * - Missing error handling for transport startup\n * - Not cleaning up resources on shutdown\n */\n\nimport path from 'node:path';\nimport { DEFAULT_PORT_RANGE } from '@agimon-ai/foundation-port-registry';\nimport {\n  createProcessLease,\n  type ProcessLease,\n  ProcessRegistryService,\n  resolveSiblingRegistryPath,\n} from '@agimon-ai/foundation-process-registry';\nimport { Command } from 'commander';\nimport { getCommandConfig, resolveConfiguredOption } from '../config.js';\nimport { createMcpContainer, PLAYWRIGHT_TYPES } from '../container/index.js';\nimport type { ToolFilterConfig } from '../server/proxy.js';\nimport { createProxyServer } from '../server/proxy.js';\nimport { CHROME_FOR_TESTING_PATH_ENV_VAR } from '../services/ChromeForTestingService.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR } from '../services/IdleCleanupService.js';\nimport { McpOwnerTrackerRegistry } from '../services/McpOwnerTrackerRegistry.js';\nimport type { AcquiredMcpPorts, McpPortAllocationService } from '../services/McpPortAllocationService.js';\nimport { McpSessionTracker } from '../services/McpSessionTracker.js';\nimport { PROXY_CONFIG_DIR_ENV_VAR } from '../services/ProxyConfigService.js';\nimport { StdioTransportHandler } from '../transports/stdio.js';\nimport { StreamableHttpTransportHandler } from '../transports/streamable-http.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost, getPlaywrightPort } from '../utils/networkConfig.js';\nimport { OWNER_HEADER, resolveOwnerId } from '../utils/ownerContext.js';\nimport { resolveWorkspaceRoot } from '../utils/workspaceRoot.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst TRANSPORT_STDIO = 'stdio';\nconst TRANSPORT_HTTP = 'http';\nconst TRANSPORT_STREAMABLE_HTTP = 'streamable-http';\nconst SUPPORTED_TRANSPORTS = new Set([TRANSPORT_STDIO, TRANSPORT_HTTP, TRANSPORT_STREAMABLE_HTTP]);\n\nconst BROWSER_CHROMIUM = 'chromium';\nconst BROWSER_FIREFOX = 'firefox';\nconst BROWSER_WEBKIT = 'webkit';\nconst SUPPORTED_BROWSERS = new Set([BROWSER_CHROMIUM, BROWSER_FIREFOX, BROWSER_WEBKIT]);\n\nconst MODE_PLAYWRIGHT = 'playwright';\nconst MODE_EXTENSION = 'extension';\nconst MODE_VM = 'vm';\nconst MODE_STEALTH = 'stealth';\nconst SUPPORTED_LAUNCH_MODES = new Set([MODE_PLAYWRIGHT, MODE_EXTENSION, MODE_VM, MODE_STEALTH]);\n\nconst BROWSER_CLOSE_TOOL = 'browser_close';\nconst CSV_SEPARATOR = ',';\n\nconst HTTP_STATUS_SPAWNED = 'spawned';\nconst HTTP_STATUS_REUSED = 'reused';\n\nconst SIGNAL_SIGINT = 'SIGINT';\nconst SIGNAL_SIGTERM = 'SIGTERM';\n\nconst ENV_REGISTRY_DIR = 'PLAYWRIGHT_REGISTRY_DIR';\nconst ENV_PORT_REGISTRY_PATH = 'PORT_REGISTRY_PATH';\nconst ENV_PIDS_DIR = 'PLAYWRIGHT_PIDS_DIR';\nconst ENV_PROFILES_DIR = 'PLAYWRIGHT_PROFILES_DIR';\nconst ENV_HOST = 'PLAYWRIGHT_HOST';\nconst ENV_PORT = 'PLAYWRIGHT_PORT';\nconst ENV_MCP_PORT = 'PLAYWRIGHT_MCP_PORT';\nconst PROFILE_HEADER = 'x-profile';\n\nconst MCP_STREAMABLE_SERVICE_NAME = 'browse-tool-mcp-http';\nconst MCP_SERVICE_NAME = 'browse-tool-mcp';\nconst MCP_SERVICE_TYPE = 'tool' as const;\nconst PROCESS_SERVICE_TYPE = 'service' as const;\n\nconst EXIT_CODE_SUCCESS = 0;\nconst EXIT_CODE_FAILURE = 1;\nconst DEFAULT_STREAMABLE_HTTP_PORT = DEFAULT_PORT_RANGE.min;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Supported browser types */\nexport type BrowserType = 'chromium' | 'firefox' | 'webkit';\n\n/** Supported launch modes */\nexport type LaunchMode = 'playwright' | 'extension' | 'vm' | 'stealth';\n\n/** CLI options for mcp-serve command */\nexport interface McpServeOptions {\n  type: string;\n  browser: BrowserType;\n  headless: boolean;\n  profile?: string;\n  mode?: LaunchMode;\n  registryPath?: string;\n  host?: string;\n  port?: string | number;\n  httpPort?: string | number;\n  registryDir?: string;\n  pidsDir?: string;\n  profilesDir?: string;\n  proxyConfigDir?: string;\n  tags?: string;\n  exclude?: string;\n  customTools?: string;\n  chromeForTestingPath?: string;\n  snippetsDir?: string;\n  idleTimeout?: string;\n}\n\n/** Interface for transport handlers */\ninterface TransportHandler {\n  start(): Promise<void>;\n  stop(): Promise<void>;\n}\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport class InvalidTransportError extends Error {\n  readonly code = 'INVALID_TRANSPORT';\n  readonly recovery = `Use --type ${[...SUPPORTED_TRANSPORTS].join(', ')}`;\n  readonly transportType: string;\n\n  constructor(transportType: string, options?: ErrorOptions) {\n    super(`Unknown transport type: ${transportType}`, options);\n    this.name = 'InvalidTransportError';\n    this.transportType = transportType;\n  }\n}\n\nexport class InvalidBrowserTypeError extends Error {\n  readonly code = 'INVALID_BROWSER_TYPE';\n  readonly recovery = `Use --browser ${[...SUPPORTED_BROWSERS].join(', ')}`;\n  readonly browserType: string;\n\n  constructor(browserType: string, options?: ErrorOptions) {\n    super(`Unknown browser type: ${browserType}`, options);\n    this.name = 'InvalidBrowserTypeError';\n    this.browserType = browserType;\n  }\n}\n\nexport class InvalidLaunchModeError extends Error {\n  readonly code = 'INVALID_LAUNCH_MODE';\n  readonly recovery = `Use --mode ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`;\n  readonly launchMode: string;\n\n  constructor(launchMode: string, options?: ErrorOptions) {\n    super(`Unknown launch mode: ${launchMode}`, options);\n    this.name = 'InvalidLaunchModeError';\n    this.launchMode = launchMode;\n  }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction parseBrowserType(value: string): BrowserType {\n  const browserType = value.toLowerCase();\n  if (!SUPPORTED_BROWSERS.has(browserType)) {\n    throw new InvalidBrowserTypeError(value);\n  }\n  return browserType as BrowserType;\n}\n\nfunction parseLaunchMode(value: string): LaunchMode {\n  const launchMode = value.toLowerCase();\n  if (!SUPPORTED_LAUNCH_MODES.has(launchMode)) {\n    throw new InvalidLaunchModeError(value);\n  }\n  return launchMode as LaunchMode;\n}\n\nfunction parsePort(value: string | number): number {\n  const port = typeof value === 'number' ? value : Number.parseInt(value, 10);\n  if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n    throw new Error(`Invalid port: ${value}`);\n  }\n  return port;\n}\n\nfunction hasExplicitValue(value: unknown): boolean {\n  if (typeof value === 'string') {\n    return value.trim() !== '';\n  }\n  return typeof value === 'number' || typeof value === 'boolean';\n}\n\nfunction normalizeTransportType(value: string): string {\n  const transportType = value.toLowerCase();\n  return transportType === TRANSPORT_HTTP ? TRANSPORT_STREAMABLE_HTTP : transportType;\n}\n\nfunction getHeaderValue(headers: Record<string, string | string[] | undefined>, name: string): string | undefined {\n  const rawValue = headers[name];\n  const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;\n  return value?.trim() || undefined;\n}\n\n/**\n * Parse a comma-separated string into a trimmed, non-empty string array.\n */\nfunction parseCsv(value: string): string[] {\n  return value\n    .split(CSV_SEPARATOR)\n    .map((s) => s.trim())\n    .filter((s) => s.length > 0);\n}\n\n/**\n * Build a ToolFilterConfig from the raw --tags and --exclude CLI strings.\n * Returns undefined when no filtering is configured.\n */\nfunction buildToolFilter(options: McpServeOptions): ToolFilterConfig | undefined {\n  const tags = options.tags ? parseCsv(options.tags) : undefined;\n  const exclude = options.exclude ? parseCsv(options.exclude) : undefined;\n\n  if (!tags?.length && !exclude?.length) {\n    return undefined;\n  }\n\n  return { tags, exclude };\n}\n\n/**\n * Typed error for browser cleanup failures with structured context.\n */\nexport class BrowserCleanupError extends Error {\n  readonly code = 'BROWSER_CLEANUP_FAILED';\n  readonly recovery = 'Browsers may still be running; check the HTTP server or kill processes manually';\n  readonly failedBrowserIds: string[];\n  readonly httpBaseUrl: string;\n\n  constructor(failedBrowserIds: string[], httpBaseUrl: string, options?: ErrorOptions) {\n    super(`Failed to close ${failedBrowserIds.length} browser(s): ${failedBrowserIds.join(', ')}`, options);\n    this.name = 'BrowserCleanupError';\n    this.failedBrowserIds = failedBrowserIds;\n    this.httpBaseUrl = httpBaseUrl;\n  }\n}\n\n/**\n * Typed error for shutdown failures with structured context.\n */\nexport class SessionShutdownError extends Error {\n  readonly code = 'SESSION_SHUTDOWN_FAILED';\n  readonly recovery = 'Check logs for cleanup and transport errors; processes may need manual cleanup';\n  readonly signal: string;\n\n  constructor(signal: string, options?: ErrorOptions) {\n    super(`Shutdown failed for signal ${signal}`, options);\n    this.name = 'SessionShutdownError';\n    this.signal = signal;\n  }\n}\n\n/**\n * Typed error for HTTP server startup failures.\n */\nexport class HttpServerStartError extends Error {\n  readonly code = 'HTTP_SERVER_START_FAILED';\n  readonly recovery = 'Check if the HTTP server binary exists and the port is available';\n\n  constructor(options?: ErrorOptions) {\n    super('Failed to start HTTP server', options);\n    this.name = 'HttpServerStartError';\n  }\n}\n\n/**\n * Typed error for MCP server bootstrap failures.\n */\nexport class McpServerBootstrapError extends Error {\n  readonly code = 'MCP_SERVER_BOOTSTRAP_FAILED';\n  readonly recovery = 'Check if all dependencies are installed and try again';\n\n  constructor(options?: ErrorOptions) {\n    super('Failed to start MCP server', options);\n    this.name = 'McpServerBootstrapError';\n  }\n}\n\n/**\n * Close browsers tracked in session via HTTP server.\n * Validates HTTP response status before reporting success.\n * Only clears tracker state when all browsers close successfully.\n */\nasync function closeSessionBrowsers(\n  sessionTracker: McpSessionTracker,\n  httpBaseUrl: string,\n  ownerId?: string,\n): Promise<void> {\n  const browserIds = sessionTracker.getBrowserIds();\n  const failedIds: string[] = [];\n  let firstCause: Error | undefined;\n\n  for (const browserId of browserIds) {\n    try {\n      const response = await fetch(`${httpBaseUrl}/execute`, {\n        method: 'POST',\n        headers: ownerId\n          ? { 'Content-Type': 'application/json', [OWNER_HEADER]: ownerId }\n          : { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n          tool: BROWSER_CLOSE_TOOL,\n          arguments: { browserId },\n        }),\n      });\n\n      if (!response.ok) {\n        const body = await response.text().catch(() => '');\n        throw new Error(`HTTP ${response.status} from ${httpBaseUrl}/execute: ${body}`);\n      }\n\n      console.error(`  Closed browser: ${browserId}`);\n    } catch (error) {\n      const cause = error instanceof Error ? error : new Error(String(error));\n      console.error(`  Failed to close browser: ${browserId}`, cause);\n      failedIds.push(browserId);\n      firstCause ??= cause;\n    }\n  }\n\n  if (failedIds.length > 0) {\n    throw new BrowserCleanupError(failedIds, httpBaseUrl, { cause: firstCause });\n  }\n\n  sessionTracker.clear();\n}\n\n/**\n * Start MCP server with session-aware cleanup.\n * Uses process.once and an isShuttingDown guard for idempotent shutdown.\n * Always attempts handler.stop() via finally to prevent resource leaks.\n */\nasync function startServerWithSessionCleanup(\n  handler: TransportHandler,\n  sessionTracker: McpSessionTracker,\n  httpBaseUrl: string,\n  ownerId?: string,\n  onShutdown?: () => Promise<void>,\n): Promise<void> {\n  await handler.start();\n\n  let isShuttingDown = false;\n\n  const shutdown = async (signal: string) => {\n    if (isShuttingDown) {\n      return;\n    }\n    isShuttingDown = true;\n\n    console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n    let cleanupError: Error | undefined;\n\n    try {\n      const state = sessionTracker.getSessionState();\n      if (state.totalBrowsers > 0) {\n        console.error(`  Cleaning up ${state.totalBrowsers} browser(s) from this session...`);\n        await closeSessionBrowsers(sessionTracker, httpBaseUrl, ownerId);\n      }\n    } catch (error) {\n      cleanupError = error instanceof Error ? error : new Error(String(error));\n      console.error('Browser cleanup error:', cleanupError);\n    } finally {\n      try {\n        await handler.stop();\n      } catch (stopError) {\n        const cause = stopError instanceof Error ? stopError : new Error(String(stopError));\n        console.error('Transport stop error:', cause);\n        cleanupError ??= cause;\n      }\n\n      try {\n        await onShutdown?.();\n      } catch (shutdownError) {\n        const cause = shutdownError instanceof Error ? shutdownError : new Error(String(shutdownError));\n        console.error('Process registry cleanup error:', cause);\n        cleanupError ??= cause;\n      }\n\n      if (cleanupError) {\n        const shutdownError = new SessionShutdownError(signal, { cause: cleanupError });\n        console.error('Error during shutdown:', shutdownError);\n        process.exit(EXIT_CODE_FAILURE);\n      }\n\n      process.exit(EXIT_CODE_SUCCESS);\n    }\n  };\n\n  process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n  process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n\n  // An MCP client that is killed rather than asked to stop never signals us; the\n  // only thing we observe is stdin closing. Without this the session's browsers\n  // and the process lease survive until the idle reaper notices.\n  process.stdin.once('end', () => void shutdown('stdin EOF'));\n  process.stdin.once('close', () => void shutdown('stdin close'));\n\n  process.on('uncaughtException', (error) => {\n    console.error('Uncaught exception:', error);\n    void shutdown('uncaughtException');\n  });\n  process.on('unhandledRejection', (reason) => {\n    console.error('Unhandled rejection:', reason);\n    void shutdown('unhandledRejection');\n  });\n}\n\n// ============================================================================\n// Command Definition\n// ============================================================================\n\nexport const mcpServeCommand = new Command('mcp-serve')\n  .description('Start Playwright MCP server for browser automation')\n  .option(\n    '-t, --type <type>',\n    `Transport type: ${[TRANSPORT_STDIO, TRANSPORT_STREAMABLE_HTTP].join(', ')}`,\n    TRANSPORT_STDIO,\n  )\n  .option('-b, --browser <browser>', `Default browser type: ${[...SUPPORTED_BROWSERS].join(', ')}`, BROWSER_CHROMIUM)\n  .option('--headless', 'Run browsers in headless mode by default', false)\n  .option('--no-headless', 'Run browsers in headed mode (visible window)')\n  .option('--host <host>', 'Default host for HTTP services', getPlaywrightHost())\n  .option('--port <port>', 'Port for streamable HTTP transport', String(DEFAULT_STREAMABLE_HTTP_PORT))\n  .option('--http-port <port>', 'Port for the inner browse-tool HTTP service')\n  .option('-p, --profile <name>', 'Default profile name for browser sessions')\n  .option('-m, --mode <mode>', `Default launch mode: ${[...SUPPORTED_LAUNCH_MODES].join(', ')}`)\n  .option('--tags <tags>', 'Comma-separated tags to filter tools (e.g. input,navigation,snapshot)')\n  .option('--exclude <tools>', 'Comma-separated tool names to exclude (applied after --tags)')\n  .option('--custom-tools <path>', 'Path to a folder containing tools.yaml and custom tool scripts')\n  .option('--chrome-for-testing-path <path>', 'Path to a Chrome for Testing executable for the inner HTTP server')\n  .option('--snippets-dir <path>', 'Path to a folder where browser_run_code snippets are stored')\n  .option('--idle-timeout <minutes>', 'Idle timeout in minutes before auto-closing browsers')\n  .option('--registry-path <path>', 'Custom registry path or directory for service discovery')\n  .option('--registry-dir <path>', 'Custom registry directory for service discovery')\n  .option('--pids-dir <path>', 'Custom PIDs directory for process tracking')\n  .option('--profiles-dir <path>', 'Custom profiles directory for browser profiles')\n  .option('--proxy-config-dir <path>', 'Custom proxy config directory')\n  .action(async function (this: Command, options: McpServeOptions) {\n    const commandDefaults = getCommandConfig<{\n      type?: string;\n      browser?: BrowserType;\n      headless?: boolean;\n      profile?: string;\n      mode?: LaunchMode;\n      host?: string;\n      port?: string | number;\n      httpPort?: string | number;\n      tags?: string;\n      exclude?: string;\n      customTools?: string;\n      chromeForTestingPath?: string;\n      snippetsDir?: string;\n      idleTimeout?: number;\n      registryPath?: string;\n      registryDir?: string;\n      pidsDir?: string;\n      profilesDir?: string;\n      proxyConfigDir?: string;\n    }>('mcpServe');\n\n    const resolvedOptions: McpServeOptions = {\n      type: resolveConfiguredOption(this, 'type', options.type, commandDefaults.type),\n      browser: resolveConfiguredOption(this, 'browser', options.browser, commandDefaults.browser),\n      headless: resolveConfiguredOption(this, 'headless', options.headless, commandDefaults.headless),\n      profile: resolveConfiguredOption(this, 'profile', options.profile, commandDefaults.profile),\n      mode: resolveConfiguredOption(this, 'mode', options.mode, commandDefaults.mode),\n      host: resolveConfiguredOption(this, 'host', options.host, commandDefaults.host, process.env.PLAYWRIGHT_HOST),\n      port: resolveConfiguredOption(this, 'port', options.port, commandDefaults.port, process.env[ENV_MCP_PORT]),\n      httpPort: resolveConfiguredOption(\n        this,\n        'httpPort',\n        options.httpPort,\n        commandDefaults.httpPort,\n        process.env[ENV_PORT],\n      ),\n      tags: resolveConfiguredOption(this, 'tags', options.tags, commandDefaults.tags),\n      exclude: resolveConfiguredOption(this, 'exclude', options.exclude, commandDefaults.exclude),\n      customTools: resolveConfiguredOption(this, 'customTools', options.customTools, commandDefaults.customTools),\n      chromeForTestingPath: resolveConfiguredOption(\n        this,\n        'chromeForTestingPath',\n        options.chromeForTestingPath,\n        commandDefaults.chromeForTestingPath,\n        process.env[CHROME_FOR_TESTING_PATH_ENV_VAR],\n      ),\n      snippetsDir: resolveConfiguredOption(this, 'snippetsDir', options.snippetsDir, commandDefaults.snippetsDir),\n      idleTimeout: resolveConfiguredOption(\n        this,\n        'idleTimeout',\n        options.idleTimeout,\n        commandDefaults.idleTimeout !== undefined ? String(commandDefaults.idleTimeout) : undefined,\n        process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR],\n      ),\n      registryPath: resolveConfiguredOption(\n        this,\n        'registryPath',\n        options.registryPath,\n        commandDefaults.registryPath,\n        process.env.PLAYWRIGHT_REGISTRY_PATH ?? process.env.PORT_REGISTRY_PATH,\n      ),\n      registryDir: resolveConfiguredOption(\n        this,\n        'registryDir',\n        options.registryDir,\n        commandDefaults.registryDir,\n        process.env.PLAYWRIGHT_REGISTRY_DIR,\n      ),\n      pidsDir: resolveConfiguredOption(\n        this,\n        'pidsDir',\n        options.pidsDir,\n        commandDefaults.pidsDir,\n        process.env.PLAYWRIGHT_PIDS_DIR,\n      ),\n      profilesDir: resolveConfiguredOption(\n        this,\n        'profilesDir',\n        options.profilesDir,\n        commandDefaults.profilesDir,\n        process.env.PLAYWRIGHT_PROFILES_DIR,\n      ),\n      proxyConfigDir: resolveConfiguredOption(\n        this,\n        'proxyConfigDir',\n        options.proxyConfigDir,\n        commandDefaults.proxyConfigDir,\n        process.env[PROXY_CONFIG_DIR_ENV_VAR],\n      ),\n    };\n    const transportType = normalizeTransportType(resolvedOptions.type);\n    const repositoryPath = resolveWorkspaceRoot();\n    const environment = process.env.NODE_ENV || 'development';\n\n    let processLease: ProcessLease | undefined;\n    let acquiredPorts: AcquiredMcpPorts | undefined;\n\n    try {\n      if (!SUPPORTED_TRANSPORTS.has(transportType)) {\n        throw new InvalidTransportError(transportType);\n      }\n\n      const browserType = parseBrowserType(resolvedOptions.browser);\n      const defaultMode = resolvedOptions.mode ? parseLaunchMode(resolvedOptions.mode) : undefined;\n      const transportPort = parsePort(resolvedOptions.port ?? DEFAULT_STREAMABLE_HTTP_PORT);\n      const explicitHttpPort = hasExplicitValue(resolvedOptions.httpPort);\n      const preferredHttpPort = explicitHttpPort ? parsePort(resolvedOptions.httpPort!) : undefined;\n      const toolFilter = buildToolFilter(resolvedOptions);\n      const customToolsDir = resolvedOptions.customTools ? path.resolve(resolvedOptions.customTools) : undefined;\n      const chromeForTestingPath = resolvedOptions.chromeForTestingPath\n        ? path.resolve(resolvedOptions.chromeForTestingPath)\n        : undefined;\n      const snippetsDir = resolvedOptions.snippetsDir ? path.resolve(resolvedOptions.snippetsDir) : undefined;\n\n      console.error('Playwright MCP Server starting...');\n      console.error(`  Transport: ${transportType}`);\n      if (transportType === TRANSPORT_STREAMABLE_HTTP) {\n        console.error(`  MCP endpoint: http://${resolvedOptions.host ?? getPlaywrightHost()}:${transportPort}/mcp`);\n      }\n      console.error(`  Default browser: ${browserType}`);\n      console.error(`  Headless: ${resolvedOptions.headless}`);\n      if (defaultMode) {\n        console.error(`  Default mode: ${defaultMode}`);\n      }\n      if (resolvedOptions.profile) {\n        console.error(`  Profile: ${resolvedOptions.profile}`);\n      }\n      if (toolFilter?.tags?.length) {\n        console.error(`  Tags: ${toolFilter.tags.join(', ')}`);\n      }\n      if (toolFilter?.exclude?.length) {\n        console.error(`  Exclude: ${toolFilter.exclude.join(', ')}`);\n      }\n      if (customToolsDir) {\n        console.error(`  Custom tools: ${customToolsDir}`);\n      }\n      if (chromeForTestingPath) {\n        console.error(`  Chrome for Testing path: ${chromeForTestingPath}`);\n      }\n      if (snippetsDir) {\n        console.error(`  Snippets dir: ${snippetsDir}`);\n      }\n      if (resolvedOptions.idleTimeout) {\n        console.error(`  Idle timeout: ${resolvedOptions.idleTimeout} minutes`);\n      }\n\n      const registryPath = resolvedOptions.registryPath || resolvedOptions.registryDir;\n      if (registryPath) {\n        process.env[ENV_REGISTRY_DIR] = registryPath;\n        process.env[ENV_PORT_REGISTRY_PATH] = registryPath;\n        process.env.PROCESS_REGISTRY_PATH = resolveSiblingRegistryPath(registryPath, 'processes.json')!;\n        console.error(`  Registry path: ${registryPath}`);\n      }\n      if (resolvedOptions.pidsDir) {\n        process.env[ENV_PIDS_DIR] = resolvedOptions.pidsDir;\n        console.error(`  PIDs dir: ${resolvedOptions.pidsDir}`);\n      }\n      if (resolvedOptions.profilesDir) {\n        process.env[ENV_PROFILES_DIR] = resolvedOptions.profilesDir;\n        console.error(`  Profiles dir: ${resolvedOptions.profilesDir}`);\n      }\n      if (resolvedOptions.proxyConfigDir) {\n        process.env[PROXY_CONFIG_DIR_ENV_VAR] = path.resolve(resolvedOptions.proxyConfigDir);\n        console.error(`  Proxy config dir: ${process.env[PROXY_CONFIG_DIR_ENV_VAR]}`);\n      }\n      if (snippetsDir) {\n        process.env.BROWSE_TOOL_SNIPPETS_DIR = snippetsDir;\n      }\n      if (chromeForTestingPath) {\n        process.env[CHROME_FOR_TESTING_PATH_ENV_VAR] = chromeForTestingPath;\n      }\n      if (resolvedOptions.idleTimeout) {\n        process.env[BROWSER_IDLE_TIMEOUT_MINUTES_ENV_VAR] = resolvedOptions.idleTimeout;\n      }\n      if (resolvedOptions.host) {\n        process.env[ENV_HOST] = resolvedOptions.host;\n      }\n\n      const localContainer = createMcpContainer();\n      const processRegistry = localContainer.get<ProcessRegistryService>(PLAYWRIGHT_TYPES.ProcessRegistryService);\n      const portAllocationService = localContainer.get<McpPortAllocationService>(\n        PLAYWRIGHT_TYPES.McpPortAllocationService,\n      );\n      acquiredPorts = await portAllocationService.acquirePorts({\n        repositoryPath,\n        environment,\n        host: resolvedOptions.host ?? getPlaywrightHost(),\n        pid: process.pid,\n        preferredMcpPort: transportType === TRANSPORT_STREAMABLE_HTTP ? transportPort : undefined,\n        reserveMcpPort: transportType === TRANSPORT_STREAMABLE_HTTP,\n        mcpServiceName: MCP_STREAMABLE_SERVICE_NAME,\n        mcpServiceType: MCP_SERVICE_TYPE,\n        mcpMetadata:\n          transportType === TRANSPORT_STREAMABLE_HTTP\n            ? {\n                healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host ?? getPlaywrightHost(), transportPort)}/health`,\n                transport: TRANSPORT_STREAMABLE_HTTP,\n              }\n            : undefined,\n      });\n      // Let ensureRunning decide the port: it discovers a healthy daemon first\n      // and only allocates when it actually has to spawn one. Pre-picking a port\n      // here would hide the daemon that is already serving this repo.\n      const httpServerManager = localContainer.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n      const httpStatus = await httpServerManager.ensureRunning(preferredHttpPort ?? getPlaywrightPort(), {\n        exactPort: explicitHttpPort,\n      });\n\n      if (!httpStatus.running || httpStatus.port === undefined) {\n        throw new HttpServerStartError();\n      }\n\n      process.env[ENV_PORT] = httpStatus.port.toString();\n\n      const httpBaseUrl = buildPlaywrightBaseUrl(getPlaywrightHost(), httpStatus.port);\n      const httpLabel = httpStatus.spawned ? HTTP_STATUS_SPAWNED : HTTP_STATUS_REUSED;\n      console.error(`  HTTP server: ${httpLabel} on port ${httpStatus.port}`);\n\n      if (transportType === TRANSPORT_STDIO) {\n        const sessionTracker = new McpSessionTracker();\n        // Stdio serves exactly one client, and this process inherits the agent's\n        // env, so this resolves to the same owner the agent's CLI calls use.\n        // A page created through either path is then usable through the other.\n        const ownerId = resolveOwnerId();\n        const handler = new StdioTransportHandler(() =>\n          createProxyServer({\n            httpBaseUrl,\n            sessionTracker,\n            defaultMode,\n            toolFilter,\n            customToolsDir,\n            enforcedProfileName: resolvedOptions.profile,\n            proxyConfigDir: process.env[PROXY_CONFIG_DIR_ENV_VAR],\n            ownerId,\n          }),\n        );\n        processLease = await createProcessLease(\n          {\n            repositoryPath,\n            serviceName: MCP_SERVICE_NAME,\n            serviceType: PROCESS_SERVICE_TYPE,\n            environment,\n            pid: process.pid,\n            metadata: {\n              transport: TRANSPORT_STDIO,\n              browser: browserType,\n              mode: defaultMode,\n            },\n          },\n          processRegistry,\n        );\n\n        await startServerWithSessionCleanup(handler, sessionTracker, httpBaseUrl, ownerId, async () => {\n          await processLease?.release({ kill: false });\n          await acquiredPorts?.release();\n        });\n        return;\n      }\n      const reservedPort = acquiredPorts.mcpPort;\n      if (!reservedPort) {\n        throw new Error(`Failed to reserve ${TRANSPORT_STREAMABLE_HTTP} port ${transportPort}`);\n      }\n      processLease = await createProcessLease(\n        {\n          repositoryPath,\n          serviceName: MCP_STREAMABLE_SERVICE_NAME,\n          serviceType: PROCESS_SERVICE_TYPE,\n          environment,\n          pid: process.pid,\n          port: reservedPort,\n          host: resolvedOptions.host ?? getPlaywrightHost(),\n          metadata: {\n            healthCheckUrl: `${buildPlaywrightBaseUrl(resolvedOptions.host ?? getPlaywrightHost(), reservedPort)}/health`,\n            transport: TRANSPORT_STREAMABLE_HTTP,\n          },\n        },\n        processRegistry,\n      );\n      // Modern HTTP has no MCP session identifier. Keep ownership and cleanup in\n      // application state, keyed by the explicit owner and selected browser profile.\n      const ownerRegistry = new McpOwnerTrackerRegistry((tracker, ownerId) =>\n        closeSessionBrowsers(tracker, httpBaseUrl, ownerId),\n      );\n      const resolveProfile = (headers: Record<string, string | string[] | undefined>) =>\n        getHeaderValue(headers, PROFILE_HEADER) ?? resolvedOptions.profile;\n      const handler = new StreamableHttpTransportHandler(\n        ({ headers }) => {\n          const enforcedProfileName = resolveProfile(headers);\n          const ownerId = getHeaderValue(headers, OWNER_HEADER);\n          const owner = ownerId ? ownerRegistry.get(ownerId, enforcedProfileName) : undefined;\n          return {\n            server: createProxyServer({\n              httpBaseUrl,\n              sessionTracker: owner,\n              defaultMode,\n              toolFilter,\n              customToolsDir,\n              enforcedProfileName,\n              proxyConfigDir: process.env[PROXY_CONFIG_DIR_ENV_VAR],\n              ownerId,\n              requireOwnerForToolCalls: true,\n            }),\n          };\n        },\n        {\n          host: resolvedOptions.host ?? getPlaywrightHost(),\n          port: reservedPort,\n          onRequestStart: ({ headers }) =>\n            ownerRegistry.beginRequest(getHeaderValue(headers, OWNER_HEADER), resolveProfile(headers)),\n        },\n      );\n\n      try {\n        await handler.start();\n      } catch (startError) {\n        await ownerRegistry.close();\n        try {\n          await processLease.release({ kill: false });\n        } catch (cleanupError) {\n          console.error('Process registry cleanup error:', cleanupError);\n        }\n        await acquiredPorts.release();\n        acquiredPorts = undefined;\n        throw startError;\n      }\n\n      let isShuttingDown = false;\n      const shutdown = async (signal: string) => {\n        if (isShuttingDown) {\n          return;\n        }\n        isShuttingDown = true;\n\n        console.error(`\\nReceived ${signal}, shutting down gracefully...`);\n        try {\n          await handler.stop();\n          await ownerRegistry.close();\n          try {\n            await processLease?.release({ kill: false });\n          } catch (cleanupError) {\n            console.error('Process registry cleanup error:', cleanupError);\n          }\n          await acquiredPorts?.release();\n          acquiredPorts = undefined;\n          process.exit(EXIT_CODE_SUCCESS);\n        } catch (stopError) {\n          console.error('Transport stop error:', stopError);\n          process.exit(EXIT_CODE_FAILURE);\n        }\n      };\n\n      process.once(SIGNAL_SIGINT, () => shutdown(SIGNAL_SIGINT));\n      process.once(SIGNAL_SIGTERM, () => shutdown(SIGNAL_SIGTERM));\n    } catch (error) {\n      if (processLease) {\n        try {\n          await processLease.release({ kill: false });\n        } catch (cleanupError) {\n          console.error('Process registry cleanup error:', cleanupError);\n        }\n      }\n      if (acquiredPorts) {\n        try {\n          await acquiredPorts.release();\n        } catch (releaseError) {\n          console.error('Port allocation cleanup error:', releaseError);\n        }\n      }\n      if (\n        error instanceof InvalidTransportError ||\n        error instanceof InvalidBrowserTypeError ||\n        error instanceof InvalidLaunchModeError ||\n        error instanceof HttpServerStartError\n      ) {\n        console.error(`Error [${error.code}]: ${error.message}`);\n        console.error(`Recovery: ${error.recovery}`);\n        process.exit(EXIT_CODE_FAILURE);\n      }\n\n      const cause = error instanceof Error ? error : new Error(String(error));\n      const bootstrapError = new McpServerBootstrapError({ cause });\n      console.error(`Error [${bootstrapError.code}]: ${bootstrapError.message}`, bootstrapError.cause);\n      console.error(`Recovery: ${bootstrapError.recovery}`);\n      process.exit(EXIT_CODE_FAILURE);\n    }\n  });\n","/**\n * Status Command\n *\n * Shows status of HTTP server and other diagnostics.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\nimport { buildPlaywrightBaseUrl, getPlaywrightHost } from '../utils/networkConfig.js';\n\n/**\n * Show status of browse-tool services\n */\nexport const statusCommand = new Command('status')\n  .description('Show status of HTTP server and diagnostics')\n  .action(async () => {\n    try {\n      const httpServeDefaults = getCommandConfig<{\n        host?: string;\n        registryDir?: string;\n        registryPath?: string;\n        pidsDir?: string;\n      }>('httpServe');\n      const registryPath =\n        process.env.PLAYWRIGHT_REGISTRY_PATH ??\n        process.env.PORT_REGISTRY_PATH ??\n        httpServeDefaults.registryPath ??\n        httpServeDefaults.registryDir;\n\n      if (registryPath) {\n        process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n        process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n        process.env.PORT_REGISTRY_PATH = registryPath;\n      }\n      if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n        process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n      }\n\n      console.log('browse-tool Status\\n');\n      console.log('-'.repeat(50));\n\n      // Create container to get HttpServerManager\n      const container = createContainer();\n      const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n      // Get HTTP server status\n      const httpStatus = await httpServerManager.getStatus();\n\n      if (httpStatus.running) {\n        const host = process.env.PLAYWRIGHT_HOST ?? httpServeDefaults.host ?? getPlaywrightHost();\n        console.log('HTTP Server: Running');\n        console.log(`  PID: ${httpStatus.pid}`);\n        console.log(`  Port: ${httpStatus.port}`);\n        console.log(`  Health: ${buildPlaywrightBaseUrl(host, httpStatus.port)}/health`);\n        if (httpStatus.browserCount !== undefined) {\n          console.log(`  Browsers: ${httpStatus.browserCount}`);\n        }\n      } else {\n        console.log('HTTP Server: Not Running');\n        if (httpStatus.error) {\n          console.log(`  Error: ${httpStatus.error}`);\n        }\n      }\n\n      console.log('');\n      console.log('-'.repeat(50));\n      process.exit(0);\n    } catch (error) {\n      console.error('Error getting status:', error);\n      process.exit(1);\n    }\n  });\n","/**\n * Stop Command\n *\n * Stops HTTP server and cleans up registry/PID files.\n *\n * DESIGN PATTERNS:\n * - Command pattern with Commander for CLI argument parsing\n * - Async/await pattern for asynchronous operations\n * - Error handling pattern with try-catch and proper exit codes\n *\n * CODING STANDARDS:\n * - Use async action handlers for asynchronous operations\n * - Provide clear option descriptions and default values\n * - Handle errors gracefully with process.exit()\n * - Log progress and errors to console\n * - Use Commander's .option() for inputs\n *\n * AVOID:\n * - Synchronous blocking operations in action handlers\n * - Missing error handling (always use try-catch)\n * - Hardcoded values (use options or environment variables)\n * - Not exiting with appropriate exit codes on errors\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport { PLAYWRIGHT_TYPES } from '../constants/playwright-types.js';\nimport { createContainer } from '../container/index.js';\nimport type { HttpServerManager } from '../services/HttpServerManager.js';\n\ninterface StopCommandOptions {\n  all: boolean;\n}\n\n/**\n * Stop HTTP server\n */\nexport const stopCommand = new Command('stop')\n  .description('Stop HTTP server and clean up registry/PID files')\n  .option('--all', 'Stop daemons from every worktree, not just this one', false)\n  .action(async (options: StopCommandOptions) => {\n    try {\n      const httpServeDefaults = getCommandConfig<{\n        registryDir?: string;\n        registryPath?: string;\n        pidsDir?: string;\n      }>('httpServe');\n      const registryPath =\n        process.env.PLAYWRIGHT_REGISTRY_PATH ??\n        process.env.PORT_REGISTRY_PATH ??\n        httpServeDefaults.registryPath ??\n        httpServeDefaults.registryDir;\n\n      if (registryPath) {\n        process.env.PLAYWRIGHT_REGISTRY_DIR = registryPath;\n        process.env.PLAYWRIGHT_REGISTRY_PATH = registryPath;\n        process.env.PORT_REGISTRY_PATH = registryPath;\n      }\n      if (!process.env.PLAYWRIGHT_PIDS_DIR && httpServeDefaults.pidsDir) {\n        process.env.PLAYWRIGHT_PIDS_DIR = httpServeDefaults.pidsDir;\n      }\n\n      console.log(\n        options.all ? 'Stopping browse-tool services from all worktrees...' : 'Stopping browse-tool services...',\n      );\n\n      // Create container to get HttpServerManager\n      const container = createContainer();\n      const httpServerManager = container.get<HttpServerManager>(PLAYWRIGHT_TYPES.HttpServerManager);\n\n      // Stop HTTP server\n      const stopped = await httpServerManager.stop({ allWorktrees: options.all });\n\n      if (stopped) {\n        console.log('HTTP server stopped');\n        console.log('Registry and PID files cleaned up');\n      } else {\n        console.log('No HTTP server was running');\n      }\n\n      console.log('Done!');\n      process.exit(0);\n    } catch (error) {\n      console.error('Error stopping services:', error);\n      process.exit(1);\n    }\n  });\n","const ROOT_OPTIONS_WITH_VALUES = new Set(['--config']);\n\nexport function getFirstCommandToken(argv: string[]): string | undefined {\n  for (let index = 0; index < argv.length; index += 1) {\n    const arg = argv[index];\n\n    if (ROOT_OPTIONS_WITH_VALUES.has(arg)) {\n      index += 1;\n      continue;\n    }\n\n    if ([...ROOT_OPTIONS_WITH_VALUES].some((option) => arg.startsWith(`${option}=`))) {\n      continue;\n    }\n\n    if (arg.startsWith('-')) {\n      continue;\n    }\n\n    return arg;\n  }\n\n  return undefined;\n}\n\nexport function shouldRegisterDynamicToolCommands(argv: string[], skipDynamicTools = false): boolean {\n  if (skipDynamicTools) {\n    return false;\n  }\n\n  const firstCommand = getFirstCommandToken(argv);\n  if (!firstCommand) {\n    return false;\n  }\n\n  if (firstCommand === 'tools') {\n    return true;\n  }\n\n  return firstCommand === 'help' && argv.includes('tools');\n}\n","/**\n * Command Builder Utilities for Tool Commands\n *\n * Converts tool definitions with JSON Schema to Commander.js commands.\n * Handles schema-to-CLI option conversion with proper type handling.\n *\n * DESIGN PATTERNS:\n * - Builder pattern for command construction\n * - Schema-driven command generation\n * - Type-safe option parsing\n *\n * CODING STANDARDS:\n * - Use TypeScript for type safety\n * - Handle all JSON Schema types appropriately\n * - Provide clear CLI conventions (kebab-case)\n *\n * AVOID:\n * - Manual command definitions for each tool\n * - Inconsistent naming conventions\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { Command, Option } from 'commander';\nimport { getCommandConfig, getToolConfig, resolveConfiguredOption } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { type FormatterOptions, formatError, formatToolResult, type OutputFormat } from './outputFormatter.js';\n\n/**\n * JSON Schema property definition\n */\ninterface SchemaProperty {\n  type?: string;\n  description?: string;\n  default?: unknown;\n  enum?: string[];\n  items?: { type?: string; enum?: string[] };\n  oneOf?: SchemaProperty[];\n  anyOf?: SchemaProperty[];\n  properties?: Record<string, SchemaProperty>;\n  additionalProperties?: boolean | SchemaProperty;\n  minimum?: number;\n  maximum?: number;\n}\n\n/**\n * Global options inherited by all tool commands\n */\ninterface GlobalOptions {\n  format: OutputFormat;\n  color: boolean;\n  port: string;\n}\n\nexport interface ToolCommandContext {\n  command: Command;\n  formatterOptions: FormatterOptions;\n  port: number;\n}\n\nexport type ToolCommandExecutor = (\n  args: Record<string, unknown>,\n  context: ToolCommandContext,\n) => Promise<CallToolResult>;\n\nexport interface ToolCommandDefinition {\n  definition: ToolDefinition;\n  execute?: ToolCommandExecutor;\n}\n\n/**\n * Convert snake_case to kebab-case for CLI command names\n */\nexport function toKebabCase(str: string): string {\n  return str.replace(/_/g, '-');\n}\n\n/**\n * Convert camelCase to kebab-case for CLI option names\n */\nexport function camelToKebab(str: string): string {\n  return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\nfunction getSchemaTypes(schema: SchemaProperty): Set<string> {\n  const types = new Set<string>();\n\n  if (schema.type) {\n    types.add(schema.type);\n  }\n\n  for (const variant of schema.oneOf ?? []) {\n    if (variant.type) {\n      types.add(variant.type);\n    }\n  }\n  for (const variant of schema.anyOf ?? []) {\n    if (variant.type) {\n      types.add(variant.type);\n    }\n  }\n\n  return types;\n}\n\n/**\n * Parse a CLI option value based on its JSON Schema type\n */\nexport function parseOptionValue(value: string, schema: SchemaProperty): unknown {\n  const types = getSchemaTypes(schema);\n\n  if (types.has('boolean')) {\n    if (value === 'true' || value === '1') return true;\n    if (value === 'false' || value === '0') return false;\n  }\n\n  if (schema.type === 'number' || schema.type === 'integer') {\n    const num = Number(value);\n    if (Number.isNaN(num)) {\n      throw new Error(`Invalid number: ${value}`);\n    }\n    return num;\n  }\n\n  if (schema.type === 'boolean') {\n    if (value === 'true' || value === '1') return true;\n    if (value === 'false' || value === '0') return false;\n    throw new Error(`Invalid boolean: ${value}`);\n  }\n\n  if (types.has('array') || types.has('object')) {\n    try {\n      return JSON.parse(value);\n    } catch {\n      if (schema.type === 'array' || schema.type === 'object') {\n        throw new Error(`Invalid JSON: ${value}`);\n      }\n    }\n  }\n\n  if (types.has('number') || types.has('integer')) {\n    const num = Number(value);\n    if (!Number.isNaN(num) && value.trim() !== '') {\n      return num;\n    }\n  }\n\n  return value;\n}\n\n/**\n * Build Commander option flag string from property name and schema\n */\nexport function buildOptionFlag(name: string, schema: SchemaProperty): string {\n  const kebabName = camelToKebab(name);\n  const types = getSchemaTypes(schema);\n\n  if (schema.type === 'boolean') {\n    return `--${kebabName}`;\n  }\n\n  if (types.has('boolean') && types.has('object')) {\n    return `--${kebabName} [value]`;\n  }\n\n  return `--${kebabName} <value>`;\n}\n\n/**\n * Build a Commander command from a tool definition\n */\nexport function buildToolCommand(commandDefinition: ToolCommandDefinition): Command {\n  const { definition: tool, execute } = commandDefinition;\n  const commandName = toKebabCase(tool.name);\n  const command = new Command(commandName);\n\n  command.description(tool.description);\n\n  interface JsonSchemaObject {\n    properties?: Record<string, unknown>;\n    required?: string[];\n    [key: string]: unknown;\n  }\n  const schema = tool.inputSchema as JsonSchemaObject;\n  const properties = schema.properties || {};\n  const required = new Set(schema.required || []);\n\n  for (const [propName, propSchema] of Object.entries(properties)) {\n    const prop = propSchema as SchemaProperty;\n    const flag = buildOptionFlag(propName, prop);\n    let description = prop.description || '';\n\n    if (prop.enum && prop.enum.length > 0) {\n      description += ` (choices: ${prop.enum.join(', ')})`;\n    }\n\n    if (prop.default !== undefined) {\n      description += ` (default: ${JSON.stringify(prop.default)})`;\n    }\n\n    if (required.has(propName)) {\n      description += ' [required]';\n    }\n\n    if (prop.type === 'boolean') {\n      if (prop.default === true) {\n        command.option(`--no-${camelToKebab(propName)}`, `Disable ${description}`);\n      } else {\n        command.option(flag, description, prop.default as boolean);\n      }\n    } else if (prop.enum && prop.enum.length > 0) {\n      command.addOption(new Option(flag, description).choices(prop.enum));\n    } else {\n      command.option(flag, description);\n    }\n  }\n\n  command.action(async function (this: Command) {\n    const options = this.optsWithGlobals() as Record<string, unknown> & GlobalOptions;\n    const commandDefaults = getCommandConfig<Partial<GlobalOptions>>('tools');\n    const format = resolveConfiguredOption(\n      this,\n      'format',\n      (options.format ?? 'json') as OutputFormat,\n      commandDefaults.format,\n    );\n    const color = resolveConfiguredOption(this, 'color', options.color ?? true, commandDefaults.color);\n    const portValue = resolveConfiguredOption(\n      this,\n      'port',\n      String(options.port ?? DEFAULT_MCP_PORT),\n      commandDefaults.port !== undefined ? String(commandDefaults.port) : undefined,\n      process.env.PLAYWRIGHT_PORT,\n    );\n    const formatterOptions: FormatterOptions = { format, color };\n    const toolDefaults = getToolConfig(tool.name);\n\n    try {\n      const args: Record<string, unknown> = {};\n\n      for (const [propName, propSchema] of Object.entries(properties)) {\n        const prop = propSchema as SchemaProperty;\n        const kebabName = camelToKebab(propName);\n        const optionKey = kebabName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n        const optionSource = this.getOptionValueSourceWithGlobals(optionKey);\n        let value = options[optionKey];\n\n        if (prop.type === 'boolean' && prop.default === true) {\n          value = options[optionKey];\n        }\n\n        if (\n          (optionSource === undefined || optionSource === 'default' || optionSource === 'implied') &&\n          toolDefaults[propName] !== undefined\n        ) {\n          value = toolDefaults[propName];\n        }\n\n        if (value === undefined && prop.default !== undefined) {\n          value = prop.default;\n        }\n\n        if (value !== undefined) {\n          if (typeof value === 'string' && prop.type !== 'string') {\n            args[propName] = parseOptionValue(value, prop);\n          } else {\n            args[propName] = value;\n          }\n        }\n      }\n\n      for (const reqField of required) {\n        if (args[reqField] === undefined) {\n          const kebabField = camelToKebab(reqField);\n          console.error(formatError(`Missing required option: --${kebabField}`, formatterOptions.color));\n          process.exit(1);\n        }\n      }\n\n      const port = Number.parseInt(portValue, 10);\n      // A port the user typed is a pin, not a hint: without this the client\n      // would happily attach to any healthy daemon on a different port.\n      const portSource = this.getOptionValueSourceWithGlobals('port');\n      const exactPort = portSource !== undefined && portSource !== 'default' && portSource !== 'implied';\n      const client = createToolClient({ port, exactPort });\n      const result = execute\n        ? await execute(args, { command: this, formatterOptions, port })\n        : await client.execute(tool.name, args);\n      const output = formatToolResult(result, formatterOptions);\n\n      if (output) {\n        console.log(output);\n      }\n\n      if (result.isError) {\n        process.exit(1);\n      }\n    } catch (error) {\n      console.error(formatError(error instanceof Error ? error : String(error), formatterOptions.color));\n      process.exit(1);\n    }\n  });\n\n  return command;\n}\n\n/**\n * Build commands for all tools from definitions\n */\nexport function buildAllToolCommands(tools: ToolCommandDefinition[]): Command[] {\n  return tools.map(buildToolCommand);\n}\n","/**\n * Tool Commands Registration Utilities\n *\n * Dynamically creates CLI commands from tool definitions fetched from the HTTP server.\n * Adds CLI-only shims for MCP capabilities that are not directly exposed as HTTP tools.\n *\n * DESIGN PATTERNS:\n * - Factory pattern for command creation\n * - Pure functions for data transformation\n * - Functional programming approach\n *\n * CODING STANDARDS:\n * - Export individual functions, not classes\n * - Use descriptive function names with verbs\n * - Add JSDoc comments for complex logic\n * - Keep functions small and focused\n *\n * AVOID:\n * - Side effects (mutating external state)\n * - Stateful logic (use services for state)\n * - Hardcoded tool definitions\n */\n\nimport { Command } from 'commander';\nimport { getCommandConfig } from '../config.js';\nimport type { ToolDefinition } from '../types/index.js';\nimport { buildAllToolCommands, type ToolCommandDefinition, toKebabCase } from './commandBuilder.js';\nimport type { BrowserInfo } from './httpClient.js';\nimport { createToolClient } from './httpClient.js';\nimport { DEFAULT_MCP_PORT } from './networkConfig.js';\nimport { formatError } from './outputFormatter.js';\n\nconst SESSION_TOOL_NAME = 'browser_list_session';\nconst SESSION_TOOL_DESCRIPTION = 'List browsers and pages currently tracked by the browse-tool HTTP server.';\n\n/**\n * Tool category groupings for help organization\n */\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n  browser: [\n    'browser_launch',\n    'browser_close',\n    'browser_start_recording',\n    'browser_stop_recording',\n    'browser_list_pages',\n    'browser_resize_page',\n    SESSION_TOOL_NAME,\n  ],\n  navigation: ['browser_navigate', 'browser_go_back', 'browser_go_forward', 'browser_reload', 'browser_wait_for'],\n  interaction: [\n    'browser_click',\n    'browser_type',\n    'browser_fill',\n    'browser_select',\n    'browser_hover',\n    'browser_press_key',\n    'browser_drag',\n    'browser_upload_file',\n  ],\n  extraction: ['browser_snapshot', 'browser_screenshot', 'browser_pdf'],\n  script: ['browser_evaluate_script', 'browser_run_code', 'browser_list_snippets'],\n  network: ['browser_list_network_requests', 'browser_get_network_request'],\n  dialog: ['browser_handle_dialog'],\n  tracing: ['browser_start_trace', 'browser_stop_trace'],\n  testing: ['browser_expect', 'run_spec', 'discover_specs'],\n  profile: ['browser_list_profiles', 'browser_delete_profile'],\n};\n\nconst SESSION_TOOL_DEFINITION: ToolDefinition = {\n  name: SESSION_TOOL_NAME,\n  description: SESSION_TOOL_DESCRIPTION,\n  inputSchema: {\n    type: 'object',\n    properties: {},\n    required: [],\n    additionalProperties: false,\n  },\n};\n\n/**\n * Get category for a tool name\n */\nexport function getToolCategory(toolName: string): string {\n  for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n    if (tools.includes(toolName)) {\n      return category;\n    }\n  }\n  return 'other';\n}\n\n/**\n * Create a parent command for tool commands with global options\n */\nexport function createToolsCommand(): Command {\n  return new Command('tools')\n    .description('Execute browser automation tools')\n    .option('-f, --format <format>', 'Output format: json, text, quiet', 'json')\n    .option('--no-color', 'Disable colored output')\n    .option('-p, --port <port>', 'HTTP server port', String(DEFAULT_MCP_PORT));\n}\n\nfunction formatSessionResult(browsers: BrowserInfo[]) {\n  return {\n    browserCount: browsers.length,\n    browsers: browsers.map((browser) => ({\n      browserId: browser.id,\n      profileName: browser.profileName,\n      currentPageId: browser.currentPageId,\n      pageIds: browser.pageIds,\n      createdAt: browser.createdAt,\n    })),\n  };\n}\n\nfunction createSessionToolCommandDefinition(): ToolCommandDefinition {\n  return {\n    definition: SESSION_TOOL_DEFINITION,\n    execute: async (_args, context) => {\n      const client = createToolClient({ port: context.port });\n      const browsers = await client.listBrowsers();\n\n      return {\n        content: [\n          {\n            type: 'text',\n            text: JSON.stringify(formatSessionResult(browsers), null, 2),\n          },\n        ],\n      };\n    },\n  };\n}\n\nfunction buildCommandDefinitions(tools: ToolDefinition[]): ToolCommandDefinition[] {\n  return [...tools.map((definition) => ({ definition })), createSessionToolCommandDefinition()];\n}\n\n/**\n * Register tool commands under a parent command\n * Fetches tool definitions from the HTTP server and creates corresponding CLI commands\n */\nexport async function registerToolCommands(parentCommand: Command, options?: { port?: number }): Promise<void> {\n  const commandDefaults = getCommandConfig<{ port?: number }>('tools');\n  const port = Number(options?.port ?? process.env.PLAYWRIGHT_PORT ?? commandDefaults.port ?? DEFAULT_MCP_PORT);\n\n  try {\n    const client = createToolClient({ port });\n    const tools = await client.listTools();\n    const toolCommands = buildAllToolCommands(buildCommandDefinitions(tools));\n\n    for (const cmd of toolCommands) {\n      parentCommand.addCommand(cmd);\n    }\n  } catch (error) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    process.stderr.write(`${formatError(`Failed to load tool commands: ${errorMessage}`, true)}\\n`);\n    process.stderr.write('Make sure the HTTP server is running: browse-tool http-serve\\n');\n  }\n}\n\n/**\n * Create tool commands from static definitions (for offline use)\n */\nexport function createToolCommandsFromDefinitions(tools: ToolDefinition[]): Command[] {\n  return buildAllToolCommands(buildCommandDefinitions(tools));\n}\n\n/**\n * Get help text for tool categories\n */\nexport function getCategoryHelp(): string {\n  const lines: string[] = ['Tool Categories:'];\n\n  for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n    const kebabTools = tools.map(toKebabCase).join(', ');\n    lines.push(`  ${category}: ${kebabTools}`);\n  }\n\n  return lines.join('\\n');\n}\n","#!/usr/bin/env node\nimport 'reflect-metadata/lite';\nimport { Command } from 'commander';\nimport packageJson from '../package.json' with { type: 'json' };\nimport { chromeServeCommand } from './commands/chrome-serve.js';\nimport { execCustomToolCommand, listCustomToolsCommand } from './commands/custom-tools.js';\nimport { dockerBuildCftCommand } from './commands/docker-build-cft.js';\nimport { execCommand } from './commands/exec.js';\nimport { httpServeCommand } from './commands/http-serve.js';\nimport { mcpServeCommand } from './commands/mcp-serve.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { initializeCliRuntime } from './config.js';\nimport { shouldRegisterDynamicToolCommands } from './utils/cliArgs.js';\nimport { DEFAULT_MCP_PORT } from './utils/networkConfig.js';\nimport { createToolsCommand, registerToolCommands } from './utils/toolCommands.js';\nimport { BROWSE_TOOL_WORKSPACE_ROOT_ENV, resolveWorkspaceRoot, setWorkspaceRootEnv } from './utils/workspaceRoot.js';\n\nconst SKIP_DYNAMIC_TOOLS_ENV = 'PLAYWRIGHT_SKIP_DYNAMIC_TOOL_COMMANDS';\n\nfunction resolveDynamicToolsPort(argv: string[], fallbackPort: string | number): number {\n  const args = [...argv];\n\n  for (let index = 0; index < args.length; index += 1) {\n    const arg = args[index];\n\n    if (arg === '--port' || arg === '-p') {\n      const candidate = args[index + 1];\n      const parsed = Number(candidate);\n      if (!Number.isNaN(parsed) && candidate) {\n        return parsed;\n      }\n      continue;\n    }\n\n    if (arg.startsWith('--port=')) {\n      const parsed = Number(arg.slice('--port='.length));\n      if (!Number.isNaN(parsed)) {\n        return parsed;\n      }\n      continue;\n    }\n\n    if (arg.startsWith('-p=')) {\n      const parsed = Number(arg.slice('-p='.length));\n      if (!Number.isNaN(parsed)) {\n        return parsed;\n      }\n    }\n  }\n\n  return Number(fallbackPort);\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n  const argv = process.argv.slice(2);\n  const workspaceRoot = setWorkspaceRootEnv(resolveWorkspaceRoot({ argv }));\n  const runtime = initializeCliRuntime(argv, { cwd: workspaceRoot });\n  const configuredToolsPort =\n    process.env.PLAYWRIGHT_PORT ??\n    runtime.config.commands.tools?.port ??\n    runtime.config.commands.exec?.port ??\n    DEFAULT_MCP_PORT;\n  const program = new Command();\n\n  program\n    .name('browse-tool')\n    .description(\n      'MCP server for browser automation using Playwright with profile management, page registry, and multi-browser support',\n    )\n    .option('--config <path>', 'Path to browse-tool config file or config directory')\n    .option(\n      '--workspace-root <path>',\n      `Canonical workspace root for registry scoping (${BROWSE_TOOL_WORKSPACE_ROOT_ENV})`,\n    )\n    .version(packageJson.version);\n\n  // Add all commands\n  program.addCommand(mcpServeCommand);\n  program.addCommand(httpServeCommand);\n  program.addCommand(chromeServeCommand);\n  program.addCommand(dockerBuildCftCommand);\n  program.addCommand(stopCommand);\n  program.addCommand(statusCommand);\n  program.addCommand(execCommand);\n  program.addCommand(listCustomToolsCommand);\n  program.addCommand(execCustomToolCommand);\n\n  // Create and register tool commands (dynamically generated from HTTP server)\n  const toolsCommand = createToolsCommand();\n  if (shouldRegisterDynamicToolCommands(argv, process.env[SKIP_DYNAMIC_TOOLS_ENV] === '1')) {\n    await registerToolCommands(toolsCommand, {\n      port: resolveDynamicToolsPort(argv, configuredToolsPort),\n    });\n  }\n  program.addCommand(toolsCommand);\n\n  // Parse arguments\n  await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n  console.error(error instanceof Error ? error.message : String(error));\n  process.exit(1);\n});\n"],"mappings":"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"}