"use strict";
// =============================================================================
// Pure Agent - Browser Automation Tools
// Ported from Hermes tools/browser_tool.py
// Uses CDP (Chrome DevTools Protocol) via child_process + HTTP
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanupAllBrowsers = cleanupAllBrowsers;
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const tool_registry_1 = require("../tool-registry");
const result_types_1 = require("../result-types");
const logger_1 = require("../logger");
const constants_1 = require("../constants");
const constants_2 = require("../constants");
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const COMMAND_TIMEOUT = 30_000;
const CDP_CONNECT_TIMEOUT = 10_000;
const SCREENSHOT_DIR = path.join((0, constants_1.getPureAgentHome)(), 'cache', 'screenshots');
const _sessions = new Map();
function getChromePath() {
    const candidates = (0, constants_2.isWindows)()
        ? [
            'C:/Program Files/Google/Chrome/Application/chrome.exe',
            'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
            process.env.CHROME_PATH || '',
        ]
        : [
            '/usr/bin/google-chrome',
            '/usr/bin/google-chrome-stable',
            '/usr/bin/chromium',
            '/usr/bin/chromium-browser',
            '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
            process.env.CHROME_PATH || '',
        ];
    for (const candidate of candidates) {
        if (candidate && fs.existsSync(candidate))
            return candidate;
    }
    // Try which
    try {
        const { execSync } = require('child_process');
        const found = execSync((0, constants_2.isWindows)() ? 'where chrome' : 'which google-chrome || which chromium', {
            encoding: 'utf-8',
            timeout: 5000,
            windowsHide: true,
        }).trim().split('\n')[0];
        if (found && fs.existsSync(found))
            return found;
    }
    catch { }
    return null;
}
async function getOrCreateSession(taskId = 'default') {
    const existing = _sessions.get(taskId);
    if (existing) {
        existing.lastActivity = Date.now();
        return existing;
    }
    const chromePath = getChromePath();
    if (!chromePath) {
        throw new Error('Chrome/Chromium not found. Install Chrome or set CHROME_PATH environment variable.');
    }
    const port = 9222 + _sessions.size;
    const userDataDir = path.join(os.tmpdir(), `pa-browser-${taskId}-${Date.now()}`);
    const proc = (0, child_process_1.spawn)(chromePath, [
        '--headless=new',
        '--disable-gpu',
        '--no-sandbox',
        '--disable-dev-shm-usage',
        `--remote-debugging-port=${port}`,
        `--user-data-dir=${userDataDir}`,
        '--window-size=1280,720',
    ], {
        stdio: 'ignore',
        windowsHide: true,
    });
    // Wait for CDP endpoint to be ready
    const cdpUrl = `http://127.0.0.1:${port}`;
    let wsUrl = '';
    for (let i = 0; i < 50; i++) {
        try {
            const response = await fetch(`${cdpUrl}/json/version`);
            const data = await response.json();
            wsUrl = data.webSocketDebuggerUrl || '';
            if (wsUrl)
                break;
        }
        catch {
            await new Promise(r => setTimeout(r, 200));
        }
    }
    if (!wsUrl) {
        proc.kill();
        throw new Error('Failed to connect to Chrome CDP endpoint');
    }
    const session = {
        process: proc,
        cdpUrl,
        wsUrl,
        lastActivity: Date.now(),
    };
    _sessions.set(taskId, session);
    proc.on('exit', () => {
        _sessions.delete(taskId);
        try {
            fs.rmSync(userDataDir, { recursive: true, force: true });
        }
        catch { }
    });
    (0, logger_1.getLogger)().info(`Browser session ${taskId} started (CDP: ${cdpUrl})`);
    return session;
}
// ---------------------------------------------------------------------------
// CDP command execution
// ---------------------------------------------------------------------------
async function cdpCommand(session, method, params = {}, timeout = COMMAND_TIMEOUT) {
    // Use HTTP endpoint for simple commands
    // For complex interactions, we use the /json/protocol endpoint
    const url = `${session.cdpUrl}/json`;
    // For most commands, we need to use WebSocket
    // But for simple page operations, we can use HTTP
    // Let's use a hybrid approach: HTTP for simple ops, spawn node for complex
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error(`CDP timeout: ${method}`)), timeout);
        // Use node's built-in WebSocket via a small inline script
        const script = `
      const ws = new (require('ws'))('${session.wsUrl}');
      ws.on('open', () => {
        ws.send(JSON.stringify({ id: 1, method: ${JSON.stringify(method)}, params: ${JSON.stringify(params)} }));
      });
      ws.on('message', (data) => {
        const msg = JSON.parse(data.toString());
        if (msg.id === 1) {
          console.log(JSON.stringify(msg.result || msg.error || {}));
          ws.close();
        }
      });
      ws.on('error', (err) => {
        console.error(JSON.stringify({ error: err.message }));
        ws.close();
      });
    `;
        const proc = (0, child_process_1.spawn)(process.execPath, ['-e', script], {
            timeout,
            windowsHide: true,
            stdio: ['ignore', 'pipe', 'pipe'],
            env: { ...process.env, NODE_PATH: path.join(__dirname, '..', '..', 'node_modules') },
        });
        let stdout = '';
        let stderr = '';
        proc.stdout?.on('data', (d) => { stdout += d.toString(); });
        proc.stderr?.on('data', (d) => { stderr += d.toString(); });
        proc.on('close', (code) => {
            clearTimeout(timer);
            if (code === 0 && stdout.trim()) {
                try {
                    resolve(JSON.parse(stdout.trim()));
                }
                catch {
                    resolve({ raw: stdout.trim() });
                }
            }
            else {
                reject(new Error(stderr || `CDP command failed with code ${code}`));
            }
        });
        proc.on('error', (err) => {
            clearTimeout(timer);
            reject(err);
        });
    });
}
// ---------------------------------------------------------------------------
// Simple page operations via /json HTTP API
// ---------------------------------------------------------------------------
async function getTargets(cdpUrl) {
    const response = await fetch(`${cdpUrl}/json`);
    return response.json();
}
async function evaluateJS(session, expression) {
    // Get the first page target
    const targets = await getTargets(session.cdpUrl);
    const page = targets.find((t) => t.type === 'page');
    if (!page)
        throw new Error('No page target found');
    return cdpCommand(session, 'Runtime.evaluate', {
        expression,
        returnByValue: true,
        awaitPromise: true,
    });
}
// ---------------------------------------------------------------------------
// Tool handlers
// ---------------------------------------------------------------------------
async function browserNavigate(args, context) {
    const { url } = args;
    if (!url)
        return (0, result_types_1.toolError)('Missing required parameter: url');
    // SSRF check
    if (url.startsWith('file://') || url.startsWith('data:')) {
        return (0, result_types_1.toolError)('Navigation to file:// and data: URLs is blocked');
    }
    try {
        const session = await getOrCreateSession(context.taskId);
        // Navigate via CDP
        await cdpCommand(session, 'Page.navigate', { url });
        // Wait for load
        await new Promise(r => setTimeout(r, 2000));
        // Get page title
        const titleResult = await evaluateJS(session, 'document.title');
        const title = titleResult?.result?.value || '';
        // Get element count
        const countResult = await evaluateJS(session, 'document.querySelectorAll("*").length');
        const elementCount = countResult?.result?.value || 0;
        // Get compact snapshot
        const snapshotResult = await evaluateJS(session, buildSnapshotScript(false));
        const snapshot = snapshotResult?.result?.value || '';
        return (0, result_types_1.toolResult)({
            success: true,
            url,
            title,
            snapshot: snapshot.slice(0, 8000),
            element_count: elementCount,
        });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Navigation failed: ${err.message}`);
    }
}
async function browserSnapshot(args, context) {
    const { full = false } = args;
    try {
        const session = await getOrCreateSession(context.taskId);
        const script = buildSnapshotScript(full);
        const result = await evaluateJS(session, script);
        const snapshot = result?.result?.value || '(empty page)';
        return (0, result_types_1.toolResult)({
            success: true,
            snapshot: snapshot.slice(0, 8000),
            element_count: (snapshot.match(/@\w+/g) || []).length,
        });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Snapshot failed: ${err.message}`);
    }
}
async function browserClick(args, context) {
    const { ref } = args;
    if (!ref)
        return (0, result_types_1.toolError)('Missing required parameter: ref');
    const elementRef = ref.startsWith('@') ? ref : `@${ref}`;
    try {
        const session = await getOrCreateSession(context.taskId);
        const script = `
      (() => {
        const el = document.querySelector('[data-ref="${elementRef}"]') ||
                   document.querySelector('${elementRef}');
        if (!el) return JSON.stringify({ success: false, error: 'Element not found: ${elementRef}' });
        el.scrollIntoView({ behavior: 'instant', block: 'center' });
        el.click();
        return JSON.stringify({ success: true, clicked: '${elementRef}' });
      })()
    `;
        const result = await evaluateJS(session, script);
        return result?.result?.value || (0, result_types_1.toolError)('Click failed');
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Click failed: ${err.message}`);
    }
}
async function browserType(args, context) {
    const { ref, text } = args;
    if (!ref)
        return (0, result_types_1.toolError)('Missing required parameter: ref');
    if (text === undefined)
        return (0, result_types_1.toolError)('Missing required parameter: text');
    try {
        const session = await getOrCreateSession(context.taskId);
        const escapedText = JSON.stringify(text);
        const script = `
      (() => {
        const el = document.querySelector('[data-ref="@${ref}"]') ||
                   document.querySelector('@${ref}') ||
                   document.getElementById('${ref}');
        if (!el) return JSON.stringify({ success: false, error: 'Element not found' });
        el.focus();
        el.value = ${escapedText};
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
        return JSON.stringify({ success: true, typed: ${escapedText}.length + ' chars', element: el.tagName });
      })()
    `;
        const result = await evaluateJS(session, script);
        return result?.result?.value || (0, result_types_1.toolError)('Type failed');
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Type failed: ${err.message}`);
    }
}
async function browserScroll(args, context) {
    const { direction } = args;
    if (!direction || !['up', 'down'].includes(direction)) {
        return (0, result_types_1.toolError)('Missing or invalid direction: use "up" or "down"');
    }
    try {
        const session = await getOrCreateSession(context.taskId);
        const amount = direction === 'down' ? 500 : -500;
        const result = await evaluateJS(session, `
      (() => {
        window.scrollBy(0, ${amount});
        return JSON.stringify({ success: true, scrolled: '${direction}', pixels: ${Math.abs(amount)} });
      })()
    `);
        return result?.result?.value || (0, result_types_1.toolError)('Scroll failed');
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Scroll failed: ${err.message}`);
    }
}
async function browserBack(_args, context) {
    try {
        const session = await getOrCreateSession(context.taskId);
        await cdpCommand(session, 'Page.goBack');
        await new Promise(r => setTimeout(r, 1000));
        const targets = await getTargets(session.cdpUrl);
        const page = targets.find((t) => t.type === 'page');
        return (0, result_types_1.toolResult)({ success: true, url: page?.url || 'unknown' });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Back failed: ${err.message}`);
    }
}
async function browserPress(args, context) {
    const { key } = args;
    if (!key)
        return (0, result_types_1.toolError)('Missing required parameter: key');
    try {
        const session = await getOrCreateSession(context.taskId);
        // Map key names to CDP key codes
        const keyCodeMap = {
            'Enter': 'Enter', 'Tab': 'Tab', 'Escape': 'Escape',
            'Backspace': 'Backspace', 'Delete': 'Delete',
            'ArrowUp': 'ArrowUp', 'ArrowDown': 'ArrowDown',
            'ArrowLeft': 'ArrowLeft', 'ArrowRight': 'ArrowRight',
            'Home': 'Home', 'End': 'End', 'PageUp': 'PageUp', 'PageDown': 'PageDown',
        };
        const cdpKey = keyCodeMap[key] || key;
        await cdpCommand(session, 'Input.dispatchKeyEvent', {
            type: 'keyDown',
            key: cdpKey,
            code: cdpKey,
        });
        await cdpCommand(session, 'Input.dispatchKeyEvent', {
            type: 'keyUp',
            key: cdpKey,
            code: cdpKey,
        });
        return (0, result_types_1.toolResult)({ success: true, pressed: key });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Press failed: ${err.message}`);
    }
}
async function browserGetImages(_args, context) {
    try {
        const session = await getOrCreateSession(context.taskId);
        const result = await evaluateJS(session, `
      (() => {
        const images = Array.from(document.images)
          .map(img => ({ src: img.src, alt: img.alt, width: img.naturalWidth, height: img.naturalHeight }))
          .filter(img => img.src && !img.src.startsWith('data:'));
        return JSON.stringify({ success: true, images, count: images.length });
      })()
    `);
        return result?.result?.value || (0, result_types_1.toolResult)({ success: true, images: [], count: 0 });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Get images failed: ${err.message}`);
    }
}
async function browserVision(args, context) {
    const { question } = args;
    if (!question)
        return (0, result_types_1.toolError)('Missing required parameter: question');
    try {
        const session = await getOrCreateSession(context.taskId);
        // Ensure screenshot directory exists
        if (!fs.existsSync(SCREENSHOT_DIR)) {
            fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
        }
        const screenshotPath = path.join(SCREENSHOT_DIR, `screenshot_${Date.now()}.png`);
        // Take screenshot via CDP
        const result = await cdpCommand(session, 'Page.captureScreenshot', {
            format: 'png',
            quality: 80,
        });
        if (result?.data) {
            fs.writeFileSync(screenshotPath, Buffer.from(result.data, 'base64'));
        }
        return (0, result_types_1.toolResult)({
            success: true,
            analysis: `Screenshot saved to ${screenshotPath}. Question: ${question}`,
            screenshot_path: screenshotPath,
            note: 'Use vision_analyze tool to analyze the screenshot content.',
        });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Vision failed: ${err.message}`);
    }
}
async function browserConsole(args, context) {
    const { clear = false, expression } = args;
    try {
        const session = await getOrCreateSession(context.taskId);
        if (expression) {
            // Evaluate expression
            const result = await evaluateJS(session, expression);
            return (0, result_types_1.toolResult)({
                success: true,
                result: result?.result?.value,
                result_type: result?.result?.type,
                method: 'Runtime.evaluate',
            });
        }
        // Get console messages (not easily available without event listeners)
        // Return a hint instead
        return (0, result_types_1.toolResult)({
            success: true,
            messages: [],
            note: 'Console messages require CDP event listeners. Use browser_console with expression to evaluate JS.',
        });
    }
    catch (err) {
        return (0, result_types_1.toolError)(`Console failed: ${err.message}`);
    }
}
// ---------------------------------------------------------------------------
// Snapshot script builder
// ---------------------------------------------------------------------------
function buildSnapshotScript(full) {
    return `
    (() => {
      const interactive = 'a, button, input, textarea, select, [role="button"], [role="link"], [role="tab"], [onclick]';
      const elements = full
        ? document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, li, td, th, label, a, button, input, textarea, select, [role]')
        : document.querySelectorAll(interactive);

      const lines = [];
      let ref = 0;
      for (const el of elements) {
        ref++;
        const tag = el.tagName.toLowerCase();
        const text = (el.textContent || '').trim().slice(0, 100);
        const type = el.getAttribute('type') || '';
        const value = el.value || '';
        const href = el.getAttribute('href') || '';
        const placeholder = el.getAttribute('placeholder') || '';
        const role = el.getAttribute('role') || '';

        el.setAttribute('data-ref', '@e' + ref);

        let line = '@e' + ref + ' <' + tag;
        if (role) line += ' role="' + role + '"';
        if (type) line += ' type="' + type + '"';
        if (placeholder) line += ' placeholder="' + placeholder + '"';
        if (href && tag === 'a') line += ' href="' + href.slice(0, 100) + '"';
        line += '>';
        if (text) line += ' ' + text;
        else if (value) line += ' ' + value;
        lines.push(line);
      }
      return lines.join('\\n');
    })()
  `;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
function cleanupAllBrowsers() {
    let count = 0;
    for (const [id, session] of _sessions) {
        session.process.kill('SIGTERM');
        _sessions.delete(id);
        count++;
    }
    return count;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
function registerBrowserTools() {
    // browser_navigate
    tool_registry_1.registry.register({
        name: 'browser_navigate',
        toolset: 'browser',
        schema: {
            description: 'Navigate the browser to a URL. Creates a browser session if one does not exist.',
            parameters: {
                type: 'object',
                properties: { url: { type: 'string', description: 'URL to navigate to' } },
                required: ['url'],
            },
        },
        handler: browserNavigate,
        isAsync: true,
        description: 'Navigate to URL',
        emoji: '🌐',
    });
    // browser_snapshot
    tool_registry_1.registry.register({
        name: 'browser_snapshot',
        toolset: 'browser',
        schema: {
            description: 'Take a text snapshot of the current page showing interactive elements with ref IDs. Use ref IDs with browser_click and browser_type.',
            parameters: {
                type: 'object',
                properties: {
                    full: { type: 'boolean', description: 'Include all elements (not just interactive). Default: false', default: false },
                },
                required: [],
            },
        },
        handler: browserSnapshot,
        isAsync: true,
        description: 'Snapshot page elements',
        emoji: '📸',
    });
    // browser_click
    tool_registry_1.registry.register({
        name: 'browser_click',
        toolset: 'browser',
        schema: {
            description: 'Click an element on the page by its ref ID (from browser_snapshot).',
            parameters: {
                type: 'object',
                properties: { ref: { type: 'string', description: 'Element ref ID (e.g., "e5" or "@e5")' } },
                required: ['ref'],
            },
        },
        handler: browserClick,
        isAsync: true,
        description: 'Click element',
        emoji: '🖱️',
    });
    // browser_type
    tool_registry_1.registry.register({
        name: 'browser_type',
        toolset: 'browser',
        schema: {
            description: 'Type text into an input field identified by ref ID.',
            parameters: {
                type: 'object',
                properties: {
                    ref: { type: 'string', description: 'Element ref ID' },
                    text: { type: 'string', description: 'Text to type' },
                },
                required: ['ref', 'text'],
            },
        },
        handler: browserType,
        isAsync: true,
        description: 'Type into field',
        emoji: '⌨️',
    });
    // browser_scroll
    tool_registry_1.registry.register({
        name: 'browser_scroll',
        toolset: 'browser',
        schema: {
            description: 'Scroll the page up or down.',
            parameters: {
                type: 'object',
                properties: {
                    direction: { type: 'string', enum: ['up', 'down'], description: 'Scroll direction' },
                },
                required: ['direction'],
            },
        },
        handler: browserScroll,
        isAsync: true,
        description: 'Scroll page',
        emoji: '📜',
    });
    // browser_back
    tool_registry_1.registry.register({
        name: 'browser_back',
        toolset: 'browser',
        schema: {
            description: 'Navigate back in browser history.',
            parameters: { type: 'object', properties: {}, required: [] },
        },
        handler: browserBack,
        isAsync: true,
        description: 'Go back',
        emoji: '⬅️',
    });
    // browser_press
    tool_registry_1.registry.register({
        name: 'browser_press',
        toolset: 'browser',
        schema: {
            description: 'Press a keyboard key (Enter, Tab, Escape, etc.).',
            parameters: {
                type: 'object',
                properties: {
                    key: { type: 'string', description: 'Key to press (e.g., "Enter", "Tab", "Escape")' },
                },
                required: ['key'],
            },
        },
        handler: browserPress,
        isAsync: true,
        description: 'Press key',
        emoji: '⌨️',
    });
    // browser_get_images
    tool_registry_1.registry.register({
        name: 'browser_get_images',
        toolset: 'browser',
        schema: {
            description: 'Get all images on the current page with their src, alt text, and dimensions.',
            parameters: { type: 'object', properties: {}, required: [] },
        },
        handler: browserGetImages,
        isAsync: true,
        description: 'Get page images',
        emoji: '🖼️',
    });
    // browser_vision
    tool_registry_1.registry.register({
        name: 'browser_vision',
        toolset: 'browser',
        schema: {
            description: 'Take a screenshot of the current page and save it. Use vision_analyze to analyze the screenshot content.',
            parameters: {
                type: 'object',
                properties: {
                    question: { type: 'string', description: 'What you want to know about the page' },
                },
                required: ['question'],
            },
        },
        handler: browserVision,
        isAsync: true,
        description: 'Screenshot page',
        emoji: '👁️',
    });
    // browser_console
    tool_registry_1.registry.register({
        name: 'browser_console',
        toolset: 'browser',
        schema: {
            description: 'Evaluate JavaScript in the browser page context or read console messages.',
            parameters: {
                type: 'object',
                properties: {
                    expression: { type: 'string', description: 'JavaScript expression to evaluate' },
                    clear: { type: 'boolean', description: 'Clear console before reading', default: false },
                },
                required: [],
            },
        },
        handler: browserConsole,
        isAsync: true,
        description: 'Browser JS console',
        emoji: '💻',
    });
}
// Auto-register
registerBrowserTools();
//# sourceMappingURL=browser.js.map