"use strict";
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.BrowserBookmarksTool = exports.BrowserCloseTool = exports.BrowserScreenshotTool = exports.BrowserReadTool = exports.BrowserFillTool = exports.BrowserClickTool = exports.BrowserNavigateTool = void 0;
const fs = __importStar(require("fs/promises"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
// ─── Browser page tools (Playwright-based) ─────────────────────────
//
// Navigate to URLs, read page content, click elements, fill forms,
// and take screenshots. High-risk: accesses the internet, can interact
// with live websites.
//
// Capability: browser-page-access (gated)
// Risk: high — navigates live websites, submits forms
let _pwBrowser = null;
let _pwPage = null;
let _pwBrowserCreatedAt = 0;
const BROWSER_MAX_LIFETIME_MS = 10 * 60 * 1000; // 10 minutes
async function getPlaywrightPage() {
    // Auto-close stale browsers to prevent resource leaks
    if (_pwBrowser && Date.now() - _pwBrowserCreatedAt > BROWSER_MAX_LIFETIME_MS) {
        await closeBrowser();
    }
    if (_pwPage && !_pwPage.isClosed())
        return _pwPage;
    const pw = await Promise.resolve().then(() => __importStar(require('playwright')));
    _pwBrowser = await pw.chromium.launch({ headless: true });
    _pwPage = await _pwBrowser.newPage();
    _pwBrowserCreatedAt = Date.now();
    return _pwPage;
}
async function closeBrowser() {
    if (_pwPage) {
        await _pwPage.close().catch(() => { });
        _pwPage = null;
    }
    if (_pwBrowser) {
        await _pwBrowser.close().catch(() => { });
        _pwBrowser = null;
    }
}
const MAX_TEXT_LENGTH = 8000;
const PAGE_TIMEOUT = 15_000;
/**
 * URL allowlist for browser navigation. When set, only URLs matching
 * these domain patterns are allowed. Configured via HARNESS_BROWSER_URL_ALLOWLIST
 * env var (comma-separated domains, e.g. "example.com,amazon.co.uk,*.gov.uk").
 * When empty, all URLs are allowed (subject to capability grants).
 */
function getUrlAllowlist() {
    const raw = process.env.HARNESS_BROWSER_URL_ALLOWLIST ?? '';
    return raw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
}
function isUrlAllowed(url) {
    const allowlist = getUrlAllowlist();
    if (allowlist.length === 0)
        return { allowed: true, reason: 'No URL allowlist configured.' };
    try {
        const hostname = new URL(url).hostname.toLowerCase();
        for (const pattern of allowlist) {
            if (pattern.startsWith('*.')) {
                const suffix = pattern.slice(2);
                if (hostname === suffix || hostname.endsWith('.' + suffix))
                    return { allowed: true, reason: `Matches allowlist pattern *.${suffix}` };
            }
            else {
                if (hostname === pattern)
                    return { allowed: true, reason: `Matches allowlist domain ${pattern}` };
            }
        }
        return { allowed: false, reason: `Domain "${hostname}" is not in the browser URL allowlist. Allowed: ${allowlist.join(', ')}` };
    }
    catch {
        return { allowed: false, reason: 'Invalid URL format.' };
    }
}
exports.BrowserNavigateTool = {
    name: 'browser_navigate',
    description: 'Open a URL in a headless browser and return the page title and text content. Requires browser-page-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            url: { type: 'string', description: 'The URL to navigate to' },
            wait_for: { type: 'string', description: 'Optional CSS selector to wait for before reading content' },
        },
        required: ['url'],
    },
    isReadOnly: true,
    async execute(input) {
        const url = String(input.url ?? '').trim();
        if (!url || !/^https?:\/\//i.test(url)) {
            return { success: false, output: 'A valid http/https URL is required.', error: 'invalid url' };
        }
        const urlCheck = isUrlAllowed(url);
        if (!urlCheck.allowed) {
            return { success: false, output: urlCheck.reason, error: 'url blocked' };
        }
        try {
            const page = await getPlaywrightPage();
            await page.goto(url, { timeout: PAGE_TIMEOUT, waitUntil: 'domcontentloaded' });
            const waitFor = typeof input.wait_for === 'string' ? input.wait_for.trim() : '';
            if (waitFor)
                await page.waitForSelector(waitFor, { timeout: PAGE_TIMEOUT }).catch(() => { });
            const title = await page.title();
            const text = await page.innerText('body').catch(() => '');
            const truncated = text.slice(0, MAX_TEXT_LENGTH);
            return { success: true, output: `📄 ${title}\n🔗 ${page.url()}\n\n${truncated}${text.length > MAX_TEXT_LENGTH ? '\n\n[...truncated]' : ''}` };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Browser navigation failed: ${msg}`, error: msg };
        }
    },
};
exports.BrowserClickTool = {
    name: 'browser_click',
    description: 'Click an element on the current browser page by CSS selector or text content. Requires browser-page-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            selector: { type: 'string', description: 'CSS selector of the element to click' },
            text: { type: 'string', description: 'Visible text of the element to click (alternative to selector)' },
        },
    },
    isReadOnly: false,
    async execute(input) {
        const selector = typeof input.selector === 'string' ? input.selector.trim() : '';
        const text = typeof input.text === 'string' ? input.text.trim() : '';
        if (!selector && !text) {
            return { success: false, output: 'Provide either a CSS selector or text to click.', error: 'missing target' };
        }
        try {
            const page = await getPlaywrightPage();
            if (selector) {
                await page.click(selector, { timeout: PAGE_TIMEOUT });
            }
            else {
                await page.getByText(text, { exact: false }).first().click({ timeout: PAGE_TIMEOUT });
            }
            await page.waitForLoadState('domcontentloaded', { timeout: PAGE_TIMEOUT }).catch(() => { });
            const title = await page.title();
            return { success: true, output: `✅ Clicked ${selector || `text "${text}"`}\n📄 ${title}\n🔗 ${page.url()}` };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Click failed: ${msg}`, error: msg };
        }
    },
};
exports.BrowserFillTool = {
    name: 'browser_fill',
    description: 'Fill a form field on the current browser page. Requires browser-page-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            selector: { type: 'string', description: 'CSS selector of the input/textarea' },
            label: { type: 'string', description: 'Label text of the field (alternative to selector)' },
            value: { type: 'string', description: 'Value to type into the field' },
        },
        required: ['value'],
    },
    isReadOnly: false,
    async execute(input) {
        const selector = typeof input.selector === 'string' ? input.selector.trim() : '';
        const label = typeof input.label === 'string' ? input.label.trim() : '';
        const value = String(input.value ?? '');
        if (!selector && !label) {
            return { success: false, output: 'Provide either a CSS selector or label for the field.', error: 'missing target' };
        }
        try {
            const page = await getPlaywrightPage();
            if (selector) {
                await page.fill(selector, value, { timeout: PAGE_TIMEOUT });
            }
            else {
                await page.getByLabel(label, { exact: false }).first().fill(value, { timeout: PAGE_TIMEOUT });
            }
            return { success: true, output: `✅ Filled ${selector || `field "${label}"`} with "${value.slice(0, 100)}"` };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Fill failed: ${msg}`, error: msg };
        }
    },
};
exports.BrowserReadTool = {
    name: 'browser_read',
    description: 'Read the current page content, title, and URL from the headless browser. Requires browser-page-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            selector: { type: 'string', description: 'Optional CSS selector to read only that element\'s text' },
        },
    },
    isReadOnly: true,
    async execute(input) {
        try {
            const page = await getPlaywrightPage();
            const title = await page.title();
            const url = page.url();
            const selector = typeof input.selector === 'string' ? input.selector.trim() : '';
            let text;
            if (selector) {
                text = await page.locator(selector).first().innerText({ timeout: PAGE_TIMEOUT }).catch(() => '(element not found)');
            }
            else {
                text = await page.innerText('body').catch(() => '');
            }
            const truncated = text.slice(0, MAX_TEXT_LENGTH);
            return { success: true, output: `📄 ${title}\n🔗 ${url}\n\n${truncated}${text.length > MAX_TEXT_LENGTH ? '\n\n[...truncated]' : ''}` };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Browser read failed: ${msg}`, error: msg };
        }
    },
};
exports.BrowserScreenshotTool = {
    name: 'browser_screenshot',
    description: 'Take a screenshot of the current browser page. Saves to .harness/browser/. Requires browser-page-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            filename: { type: 'string', description: 'Output filename (default: page-screenshot.png)' },
            full_page: { type: 'boolean', description: 'Capture full scrollable page (default: false)' },
        },
    },
    isReadOnly: true,
    async execute(input) {
        const filename = typeof input.filename === 'string' ? input.filename.trim() : 'page-screenshot.png';
        const fullPage = input.full_page === true;
        const safeFilename = filename.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 100);
        const outDir = path.join(process.cwd(), '.harness', 'browser');
        const outPath = path.join(outDir, safeFilename);
        try {
            await fs.mkdir(outDir, { recursive: true });
            const page = await getPlaywrightPage();
            await page.screenshot({ path: outPath, fullPage });
            const title = await page.title();
            return { success: true, output: `📸 Screenshot saved: ${outPath}\n📄 ${title}\n🔗 ${page.url()}` };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Screenshot failed: ${msg}`, error: msg };
        }
    },
};
exports.BrowserCloseTool = {
    name: 'browser_close',
    description: 'Close the headless browser session. Call this when done with browser tasks to free resources.',
    parameters: { type: 'object', properties: {} },
    isReadOnly: false,
    async execute() {
        await closeBrowser();
        return { success: true, output: '✅ Browser session closed.' };
    },
};
exports.BrowserBookmarksTool = {
    name: 'browser_bookmarks',
    description: 'Read bookmarks from Chrome or Edge browser profiles. Returns bookmark titles and URLs. Requires a browser-profile-access capability grant.',
    parameters: {
        type: 'object',
        properties: {
            browser: { type: 'string', description: 'Browser to read from: "chrome" (default) or "edge"' },
            folder: { type: 'string', description: 'Optional: filter to a specific bookmark folder name' },
            limit: { type: 'number', description: 'Maximum bookmarks to return (default 50)' },
        },
    },
    isReadOnly: true,
    async execute(input) {
        const browser = String(input.browser ?? 'chrome').trim().toLowerCase();
        const folder = typeof input.folder === 'string' ? input.folder.trim() : '';
        const limit = Math.min(200, Math.max(1, Number(input.limit) || 50));
        if (browser !== 'chrome' && browser !== 'edge') {
            return { success: false, output: 'Supported browsers: chrome, edge', error: 'unsupported browser' };
        }
        const bookmarksPath = getBookmarksPath(browser);
        if (!bookmarksPath) {
            return { success: false, output: `Could not find ${browser} bookmarks file on this platform.`, error: 'bookmarks not found' };
        }
        try {
            const raw = await fs.readFile(bookmarksPath, 'utf-8');
            const data = JSON.parse(raw);
            if (!data.roots) {
                return { success: false, output: 'Bookmarks file has unexpected format.', error: 'invalid format' };
            }
            const allBookmarks = [];
            for (const [rootName, rootValue] of Object.entries(data.roots)) {
                flattenBookmarks(rootValue, rootName, allBookmarks);
            }
            let filtered = allBookmarks;
            if (folder) {
                const folderLower = folder.toLowerCase();
                filtered = allBookmarks.filter((b) => b.folder.toLowerCase().includes(folderLower));
            }
            const limited = filtered.slice(0, limit);
            if (limited.length === 0) {
                return { success: true, output: folder ? `No bookmarks found in folder "${folder}".` : 'No bookmarks found.' };
            }
            const output = limited.map((b) => `${b.name} — ${b.url} [${b.folder}]`).join('\n');
            return {
                success: true,
                output: `Found ${filtered.length} bookmark(s)${filtered.length > limit ? ` (showing first ${limit})` : ''}:\n\n${output}`,
            };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Failed to read bookmarks: ${msg}`, error: msg };
        }
    },
};
function getBookmarksPath(browser) {
    const platform = os.platform();
    const home = os.homedir();
    if (platform === 'win32') {
        const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
        if (browser === 'chrome')
            return path.join(localAppData, 'Google', 'Chrome', 'User Data', 'Default', 'Bookmarks');
        return path.join(localAppData, 'Microsoft', 'Edge', 'User Data', 'Default', 'Bookmarks');
    }
    if (platform === 'darwin') {
        if (browser === 'chrome')
            return path.join(home, 'Library', 'Application Support', 'Google', 'Chrome', 'Default', 'Bookmarks');
        return path.join(home, 'Library', 'Application Support', 'Microsoft Edge', 'Default', 'Bookmarks');
    }
    if (platform === 'linux') {
        if (browser === 'chrome')
            return path.join(home, '.config', 'google-chrome', 'Default', 'Bookmarks');
        return path.join(home, '.config', 'microsoft-edge', 'Default', 'Bookmarks');
    }
    return null;
}
function flattenBookmarks(node, folderPath, result) {
    if (typeof node !== 'object' || node === null)
        return;
    const type = String(node.type ?? '');
    if (type === 'url' && typeof node.url === 'string') {
        result.push({
            name: String(node.name ?? '').slice(0, 200),
            url: String(node.url).slice(0, 2000),
            folder: folderPath,
        });
    }
    if (type === 'folder' && Array.isArray(node.children)) {
        const name = String(node.name ?? folderPath);
        for (const child of node.children) {
            flattenBookmarks(child, `${folderPath}/${name}`, result);
        }
    }
}
//# sourceMappingURL=browserTools.js.map