import { createLlmClient } from "../llm/client.js";
import { BrowserManager } from "../browser/manager.js";
import { BrowserActions } from "../browser/actions.js";
import { TaskRunner } from "./task-runner.js";
import { Scheduler } from "./scheduler.js";
import { MemoryManager } from "./memory.js";
import { StoreManager } from "./store.js";
import { SkillGenerator } from "./skill-generator.js";
import { ImageGenerator } from "./image-generator.js";
import { BrowserLock } from "./browser-lock.js";
import { ChromeWatchdog } from "./chrome-watchdog.js";
import { formatDuration } from "./duration.js";
import { ChatEngine, summarizeChatInput } from "./chat-engine.js";
import { formatAddTask, formatRemoveTask, formatToggleTask, formatUpdateTask, } from "./executors/scheduler-executor.js";
import { TUI } from "../tui/tui.js";
import { TelegramBot } from "../telegram/bot.js";
import { OrgManager } from "./org-manager.js";
import { ExecutionLogger } from "../logging/logger.js";
import { syncOmniFlowSessionBlob, } from "../center/session-sync.js";
import path from "node:path";
const DEFAULT_SCHEDULED_TASK_TIMEOUT_MS = 30 * 60 * 1000;
const TAB_RESET_TIMEOUT_MS = 10 * 1000;
// Uniform 0-10 min jitter applied to every cron-triggered task before lock
// acquisition. Sleeps OUTSIDE the browser lock so other tasks / chat can use
// the browser during the wait. Manual `/run` (runTaskById) is unaffected.
const SCHEDULED_TASK_JITTER_MAX_MS = 10 * 60 * 1000;
const DEFAULT_CHROME_MAX_RUNTIME_MS = 24 * 60 * 60 * 1000;
const DEFAULT_CHROME_RESTART_CHECK_INTERVAL_MS = 10 * 60 * 1000;
const DEFAULT_CHROME_RESTART_MIN_LEAD_MS = 10 * 60 * 1000;
const DEFAULT_CHROME_IDLE_CLOSE_MS = 5 * 60 * 1000;
const DEFAULT_CHROME_IDLE_CLOSE_MIN_LEAD_MS = 2 * 60 * 1000;
const DEFAULT_CHROME_IDLE_CLOSE_CHECK_INTERVAL_MS = 60 * 1000;
class ScheduledTaskTimeoutError extends Error {
    constructor(task, timeoutMs) {
        super(`Scheduled task "${task.name}" timed out after ${formatDuration(timeoutMs)}`);
        this.name = "ScheduledTaskTimeoutError";
    }
}
export { NoChatContext } from "./agent-types.js";
import { FrontendMultiplexer } from "./frontend-multiplexer.js";
export class Agent {
    config;
    llmClient;
    browser;
    browserActions;
    taskRunner;
    scheduler;
    memory;
    store;
    skillGenerator;
    imageGenerator = null;
    tui = null;
    telegramBot = null;
    orgManager = null;
    chatEngine;
    browserLock = new BrowserLock();
    chromeWatchdog;
    /** Per-session chat logger. Lazy-created on first chat turn, reset on clearChat. */
    chatLogger = null;
    attachmentSenders = new FrontendMultiplexer();
    progressNotifiers = new FrontendMultiplexer();
    constructor(config) {
        this.config = config;
        this.llmClient = createLlmClient(config);
        this.browser = new BrowserManager(config);
        this.browserActions = new BrowserActions(this.browser, config.browser.actionDelay, path.join(config.workspace, "attachments"), { allowEvaluateFallbacks: config.browser.allowEvaluateFallbacks });
        this.memory = new MemoryManager(config.workspace);
        this.store = new StoreManager(config.workspace);
        this.taskRunner = new TaskRunner(config, this.llmClient, this.browser, this.memory, this.store);
        this.scheduler = new Scheduler(config.workspace, config.scheduler?.timezone);
        this.skillGenerator = new SkillGenerator(config.workspace, this.llmClient);
        if (config.llm.imageGeneration) {
            this.imageGenerator = new ImageGenerator(config.llm.imageGeneration, config.llm.baseUrl, config.workspace);
        }
        if (config.org) {
            this.orgManager = new OrgManager(config);
        }
        // Give TaskRunner access to scheduler for auto-linking skills
        this.taskRunner.setScheduler(this.scheduler);
        this.chatEngine = new ChatEngine({
            llmClient: this.llmClient,
            browserActions: this.browserActions,
            memory: this.memory,
            store: this.store,
            scheduler: this.scheduler,
            skillGenerator: this.skillGenerator,
            imageGenerator: this.imageGenerator,
            orgManager: this.orgManager,
            orgConfig: config.org,
            llmProtocol: config.llm.protocol,
            workspace: config.workspace,
            agentName: config.name,
            maxIterations: config.maxIterations,
            getAttachmentSender: () => this.attachmentSenders.dispatcher(),
            getProgressNotifier: () => this.progressNotifiers.dispatcher(),
        });
        this.chromeWatchdog = new ChromeWatchdog({
            checkIntervalMs: this.config.scheduler?.chromeRestartCheckIntervalMs ??
                DEFAULT_CHROME_RESTART_CHECK_INTERVAL_MS,
            maxRuntimeMs: this.config.scheduler?.chromeMaxRuntimeMs ??
                DEFAULT_CHROME_MAX_RUNTIME_MS,
            minLeadMs: this.config.scheduler?.chromeRestartMinLeadMs ??
                DEFAULT_CHROME_RESTART_MIN_LEAD_MS,
            idleCloseMs: this.config.scheduler?.chromeIdleCloseMs ??
                DEFAULT_CHROME_IDLE_CLOSE_MS,
            idleCloseMinLeadMs: this.config.scheduler?.chromeIdleCloseMinLeadMs ??
                DEFAULT_CHROME_IDLE_CLOSE_MIN_LEAD_MS,
            idleCheckIntervalMs: this.config.scheduler?.chromeIdleCloseCheckIntervalMs ??
                DEFAULT_CHROME_IDLE_CLOSE_CHECK_INTERVAL_MS,
        }, {
            lock: this.browserLock,
            getUptimeMs: () => this.browser.getUptimeMs(),
            getIdleMs: () => this.browser.getIdleMs(),
            getNextRun: () => this.scheduler.getNextRun(),
            restartBrowser: (reason) => this.restartBrowser(reason),
            closeBrowser: (reason) => this.closeBrowserIdle(reason),
        });
        // Wire up scheduler to task runner (serialized via browser lock)
        this.scheduler.setHandler(async (task) => {
            // De-correlate cron fires so traffic looks less robotic. Done BEFORE
            // acquiring the lock so concurrent tasks/chat are not blocked while a
            // task is just sleeping.
            const jitterMs = Math.floor(Math.random() * SCHEDULED_TASK_JITTER_MAX_MS);
            console.log(`⏱️  Jitter for "${task.name}": ${(jitterMs / 1000).toFixed(1)}s before lock`);
            await new Promise((r) => setTimeout(r, jitterMs));
            const lock = this.acquireBrowserLock();
            await lock.promise;
            let result = null;
            let failure = null;
            let timedOut = false;
            try {
                try {
                    console.log(`🔒 Lock acquired for task: ${task.name}`);
                    // Drop the previous task's tabs so its renderer's accumulated
                    // JS heap / DOM / decoded images get released. The browser lock
                    // ensures no other operation is touching these pages right now.
                    await this.resetTabsOrRestart("before scheduled task");
                    const timeoutMs = this.config.scheduler?.taskTimeoutMs ??
                        DEFAULT_SCHEDULED_TASK_TIMEOUT_MS;
                    result = await this.runScheduledTaskWithWatchdog(task, timeoutMs, () => {
                        timedOut = true;
                    });
                    console.log(`  Task result: ${result.slice(0, 200)}`);
                }
                catch (err) {
                    failure = err;
                    timedOut ||= err instanceof ScheduledTaskTimeoutError;
                }
                try {
                    if (timedOut) {
                        await this.restartBrowser(`scheduled task timeout: ${task.name}`);
                    }
                    else {
                        await this.resetTabsOrRestart("after scheduled task");
                    }
                }
                catch (err) {
                    const msg = err instanceof Error ? err.message : String(err);
                    console.error(`  Browser recovery failed: ${msg}`);
                    failure ??= err;
                }
                if (failure) {
                    const msg = failure instanceof Error ? failure.message : String(failure);
                    console.error(`  Task failed: ${msg}`);
                    if (this.telegramBot) {
                        await this.telegramBot.notify(`❌ Task "${task.name}" failed:\n${msg.slice(0, 3800)}`);
                    }
                    throw failure;
                }
                // Notify via Telegram if available
                if (this.telegramBot && result) {
                    await this.telegramBot.notify(`✅ Task "${task.name}" completed:\n${result.slice(0, 3800)}`);
                }
            }
            finally {
                lock.release();
            }
        });
    }
    async start(opts = {}) {
        // Claim workspace ownership up front so a conflicting dipclaw fails fast
        // at startup rather than at the first browser action. Chrome itself is
        // launched lazily by the first browser-touching call (getActivePage),
        // and may be torn down + relaunched in-place by the idle-close watchdog
        // while this workspace lock stays held.
        await this.browser.prepareWorkspace();
        // The local Scheduler is for stand-alone mode. In center mode the cron
        // tick lives on the dp-center; `skipScheduler` suppresses the local
        // scheduler so it doesn't double-fire any tasks the operator happened to
        // leave in the local tasks.json. The class is otherwise untouched so
        // local-mode operators keep their workflow.
        if (!opts.skipScheduler) {
            this.scheduler.start();
        }
        this.chromeWatchdog.start();
        // Start Telegram bot if configured
        if (this.config.telegram) {
            this.telegramBot = new TelegramBot(this.config.telegram.botToken, this, this.config.telegram.allowedUsers);
            try {
                await this.telegramBot.start();
            }
            catch (err) {
                console.warn(`⚠️  Telegram bot failed to start; continuing without Telegram: ${err instanceof Error ? err.message : String(err)}`);
                this.telegramBot.stop();
                this.telegramBot = null;
            }
        }
        // Start TUI only if enabled
        if (this.config.tui) {
            this.tui = new TUI(this);
            this.tui.start();
        }
        if (this.orgManager) {
            await this.orgManager.init();
            // 如果当前角色是基础员工 (worker)，则启动后台轮询，定期检查老板是否派发了新任务
            if (this.config.org?.role === "worker") {
                this.startOrgTaskPolling();
            }
        }
    }
    /**
     * Run a center-dispatched prompt against this worker under the BrowserLock.
     *
     * The contract matches the M3 "single worker end-to-end" path in
     * docs/architecture.md: center writes a task_run row + dispatches a
     * `task_dispatch` message; the worker frontend (center-frontend.ts) calls
     * this method and is responsible for emitting `task_event` messages
     * (`dispatched_ack` immediately on receipt, `finished` when the
     * promise here resolves with the outcome).
     *
     * Why this lives on Agent and not on center-frontend: BrowserLock is owned
     * by Agent, and so is the per-turn logger wiring. Keeping the lock
     * acquisition here means center-frontend stays a pure adapter.
     */
    async enqueueTaskFromCenter(taskRunId, prompt, opts = {}) {
        const lock = this.acquireBrowserLock();
        await lock.promise;
        try {
            await this.resetTabsOrRestart(`before center task ${taskRunId}`);
            const result = await this.taskRunner.runPrompt(prompt, `center-${taskRunId.slice(0, 8)}`);
            return { status: "success", result };
        }
        catch (err) {
            const aborted = opts.signal?.aborted ||
                (err instanceof Error && err.name === "AbortError");
            const message = err instanceof Error ? err.message : String(err);
            return {
                status: aborted ? "cancelled" : "failed",
                error: message,
            };
        }
        finally {
            // Try a best-effort tab cleanup so the next task starts fresh, but
            // don't let cleanup failure mask the task outcome.
            try {
                await this.resetTabsOrRestart(`after center task ${taskRunId}`);
            }
            catch (err) {
                const msg = err instanceof Error ? err.message : String(err);
                console.error(`  center task cleanup failed: ${msg}`);
            }
            lock.release();
        }
    }
    /**
     * Pull a center-published OmniFlow session export into the local workspace.
     * Acquires the BrowserLock so the swap can't race a chat turn / task,
     * closes Chrome, updates the local session JSON only when the content hash
     * changed, and launches Chrome again so the bundled extension imports it.
     */
    async swapSession(blob, deps) {
        const lock = this.acquireBrowserLock();
        await lock.promise;
        try {
            await this.browser.close();
            const result = await syncOmniFlowSessionBlob(this.config.workspace, blob, deps);
            await this.browser.launch();
            console.log(result.status === "skipped"
                ? `📦 OmniFlow session already current (hash: ${blob.content_hash.slice(0, 12)}…)`
                : `📦 OmniFlow session applied (hash: ${blob.content_hash.slice(0, 12)}…)`);
        }
        finally {
            lock.release();
        }
    }
    /**
     * Public read of BrowserLock state — true when a task or chat turn is
     * currently using the browser (or queued ahead of us). Frontends
     * (center-frontend, Telegram bot) use this to surface "worker busy, queued"
     * to the user without taking the lock themselves.
     */
    isBusy() {
        return !this.browserLock.isIdle();
    }
    /**
     * 启动员工的组织任务轮询机制。
     * 此方法会在后台每隔 10 秒扫描一次该员工的“私有信箱”(inbox)。
     * 如果发现有老板派发的新任务，将按顺序逐一执行。
     */
    startOrgTaskPolling() {
        const POLL_INTERVAL = 10000; // 10秒轮询一次
        const poll = async () => {
            if (!this.orgManager)
                return;
            try {
                // 从信箱中取出所有状态为 "pending" 的任务
                const tasks = this.orgManager.pollTasks();
                for (const task of tasks) {
                    await this.handleOrgTask(task);
                }
            }
            catch (err) {
                console.error("Error polling org tasks:", err);
            }
            finally {
                // 确保在上一次轮询及任务完全结束后，再设置下一次定时器，防止任务堆叠阻塞
                setTimeout(poll, POLL_INTERVAL);
            }
        };
        setTimeout(poll, POLL_INTERVAL);
    }
    /**
     * 处理单一的组织指派任务。
     * 任务状态流转：pending -> running -> completed/failed
     */
    async handleOrgTask(task) {
        console.log(`Received task from ${task.assignedBy}: ${task.instruction}`);
        // 先将任务状态标记为执行中，防止重复执行
        this.orgManager?.updateTask(task.id, "running");
        // 更新员工自身的状态为 busy，老板可以据此判断员工负荷
        this.orgManager?.setStatus("busy");
        try {
            // 将老板的指令包装成一次内部的对话回合 (chat turn)，交由 LLM 大脑处理
            const result = await this.chat(`[Assignment from ${task.assignedBy}]: ${task.instruction}`);
            // 任务成功完成，将结果写回任务文件，供老板查阅
            this.orgManager?.updateTask(task.id, "completed", result);
            if (this.telegramBot) {
                await this.telegramBot.notify(`Finished task from ${task.assignedBy}:\n${task.instruction}\n\nResult: ${result.slice(0, 500)}...`);
            }
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            // 任务执行报错，记录错误信息并标记为 failed
            this.orgManager?.updateTask(task.id, "failed", msg);
            if (this.telegramBot) {
                await this.telegramBot.notify(`Failed task from ${task.assignedBy}:\n${task.instruction}\n\nError: ${msg}`);
            }
        }
        finally {
            // 任务结束（无论成功或失败），恢复员工状态为空闲
            this.orgManager?.setStatus("idle");
        }
    }
    async stop() {
        // 1. Stop frontends first so no new chat turns / scheduled tasks enqueue.
        this.chromeWatchdog.stop();
        this.scheduler.stop();
        this.telegramBot?.stop();
        this.tui?.stop();
        // 2. Wait for any in-flight chat turn / task to finish its finally block
        //    (which includes writing chat_turn_end). Only then do we emit
        //    chat_session_end, so log order stays well-formed on SIGINT/SIGTERM.
        //    If the in-flight operation is stuck the process can be force-killed
        //    with a second signal — standard Unix behavior.
        const lock = this.acquireBrowserLock();
        await lock.promise;
        try {
            if (this.chatLogger) {
                this.chatLogger.log("chat_session_end", { reason: "shutdown" });
                this.chatLogger = null;
            }
            this.orgManager?.stop();
            await this.browser.close();
        }
        finally {
            lock.release();
        }
    }
    // --- Public API for TUI/Telegram ---
    getName() {
        return this.config.name;
    }
    getWorkspace() {
        return this.config.workspace;
    }
    // --- Frontend hook registration ---
    /**
     * Register a frontend's delivery callbacks under a stable id (e.g.
     * "telegram", "center"). Multiple frontends can be registered simultaneously;
     * the Agent dispatches each tool call through every registered callback in
     * insertion order, and the first one that doesn't throw NoChatContext
     * handles it. Frontends should scope their callbacks to their own
     * AsyncLocalStorage and throw NoChatContext when the store is empty, so
     * the dispatcher can find the frontend whose chat handler is on the stack.
     */
    registerFrontend(id, hooks) {
        if (hooks.sender)
            this.attachmentSenders.set(id, hooks.sender);
        if (hooks.notifier)
            this.progressNotifiers.set(id, hooks.notifier);
    }
    /** Drop both the sender and notifier registered under `id`, if any. */
    unregisterFrontend(id) {
        this.attachmentSenders.delete(id);
        this.progressNotifiers.delete(id);
    }
    /**
     * @deprecated Use `registerFrontend("legacy", { sender })`.
     *
     * Single-slot legacy API. Pass `null` to clear the legacy slot only —
     * does not touch other registered frontends.
     */
    setAttachmentSender(sender) {
        if (sender)
            this.attachmentSenders.set("legacy", sender);
        else
            this.attachmentSenders.delete("legacy");
    }
    /** @deprecated Use `registerFrontend("legacy", { notifier })`. */
    setProgressNotifier(notifier) {
        if (notifier)
            this.progressNotifiers.set("legacy", notifier);
        else
            this.progressNotifiers.delete("legacy");
    }
    /**
     * Clear chat history and start a new session.
     *
     * Queues on the browser lock so it runs AFTER any in-flight chat turn. This
     * guarantees the turn's finally block writes `chat_turn_end` before we emit
     * `chat_session_end`, and prevents the old turn from pushing messages into
     * the cleared chatHistory.
     */
    async clearChat() {
        const lock = this.acquireBrowserLock();
        await lock.promise;
        try {
            if (this.chatLogger) {
                this.chatLogger.log("chat_session_end", { reason: "clearChat" });
                this.chatLogger = null;
            }
            this.chatEngine.reset();
        }
        finally {
            lock.release();
        }
    }
    /** Lazily create a per-session chat logger. One jsonl per chat session. */
    ensureChatLogger() {
        if (!this.chatLogger) {
            const logsDir = path.join(this.config.workspace, "logs");
            this.chatLogger = new ExecutionLogger(logsDir, "chat");
            this.chatLogger.log("chat_session_start", {
                workspace: this.config.workspace,
                model: this.config.llm?.model,
            });
        }
        return this.chatLogger;
    }
    /** Chat with the agent (LLM-driven, with tool use) */
    async chat(message) {
        const lock = this.acquireBrowserLock();
        await lock.promise;
        const logger = this.ensureChatLogger();
        this.browserActions.setLogger(logger);
        const turnStartedAt = Date.now();
        logger.log("chat_turn_start", {
            mode: "sync",
            userMessage: summarizeChatInput(message),
            historyLen: this.chatEngine.getHistoryLength(),
        });
        try {
            const result = await this.chatEngine.chat(message);
            logger.log("chat_turn_end", {
                mode: "sync",
                durationMs: Date.now() - turnStartedAt,
                resultPreview: result.slice(0, 500),
            });
            return result;
        }
        catch (err) {
            logger.log("chat_turn_end", {
                mode: "sync",
                durationMs: Date.now() - turnStartedAt,
                error: err instanceof Error ? err.message : String(err),
            }, false);
            throw err;
        }
        finally {
            this.browserActions.clearLogger(logger);
            lock.release();
        }
    }
    /** Chat with streaming — yields text deltas, handles tool calls internally */
    async *chatStream(message) {
        const lock = this.acquireBrowserLock();
        await lock.promise;
        const logger = this.ensureChatLogger();
        this.browserActions.setLogger(logger);
        const turnStartedAt = Date.now();
        logger.log("chat_turn_start", {
            mode: "stream",
            userMessage: summarizeChatInput(message),
            historyLen: this.chatEngine.getHistoryLength(),
        });
        let collected = "";
        let errored = null;
        try {
            for await (const delta of this.chatEngine.chatStream(message)) {
                collected += delta;
                yield delta;
            }
        }
        catch (err) {
            errored = err instanceof Error ? err : new Error(String(err));
            throw err;
        }
        finally {
            logger.log("chat_turn_end", {
                mode: "stream",
                durationMs: Date.now() - turnStartedAt,
                resultPreview: collected.slice(0, 500),
                ...(errored ? { error: errored.message } : {}),
            }, !errored);
            this.browserActions.clearLogger(logger);
            lock.release();
        }
    }
    listTasks() {
        return this.scheduler.formatTaskList();
    }
    addTask(name, cron, prompt, timezone) {
        return formatAddTask(this.scheduler, name, cron, prompt, timezone);
    }
    removeTask(id) {
        return formatRemoveTask(this.scheduler, id);
    }
    toggleTask(id, enabled) {
        return formatToggleTask(this.scheduler, id, enabled);
    }
    updateTask(id, patch) {
        return formatUpdateTask(this.scheduler, id, patch);
    }
    async runTaskById(id) {
        const task = this.scheduler.getTask(id);
        if (!task)
            return `Task ${id} not found.`;
        const lock = this.acquireBrowserLock();
        await lock.promise;
        try {
            const result = await this.taskRunner.runTask(task);
            this.scheduler.updateTaskStatus(id, "ok");
            return result;
        }
        catch (err) {
            this.scheduler.updateTaskStatus(id, "error");
            return `Error: ${err instanceof Error ? err.message : String(err)}`;
        }
        finally {
            lock.release();
        }
    }
    listSkills() {
        return this.skillGenerator.listSkills();
    }
    async generateSkill(logFileName) {
        const logsDir = path.join(this.config.workspace, "logs");
        const logs = ExecutionLogger.listLogs(logsDir);
        const match = logs.find((l) => l.includes(logFileName));
        if (!match)
            return `Log file not found: ${logFileName}`;
        const logPath = path.join(logsDir, match);
        return await this.skillGenerator.generate(logPath);
    }
    listMemories() {
        return this.memory.list();
    }
    async runScheduledTaskWithWatchdog(task, timeoutMs, onTimeout) {
        let timedOut = false;
        let timer = null;
        const controller = new AbortController();
        const taskPromise = this.taskRunner.runTask(task, controller.signal);
        taskPromise.catch((err) => {
            if (!timedOut)
                return;
            const msg = err instanceof Error ? err.message : String(err);
            console.error(`  Timed-out task "${task.name}" later ended after Chrome restart: ${msg}`);
        });
        const timeoutPromise = new Promise((_, reject) => {
            timer = setTimeout(() => {
                timedOut = true;
                onTimeout();
                const err = new ScheduledTaskTimeoutError(task, timeoutMs);
                controller.abort(err);
                reject(err);
            }, timeoutMs);
        });
        try {
            return await Promise.race([taskPromise, timeoutPromise]);
        }
        finally {
            if (timer)
                clearTimeout(timer);
        }
    }
    async resetTabsOrRestart(phase) {
        try {
            await withTimeout(this.browser.resetTabs(), TAB_RESET_TIMEOUT_MS, `resetTabs timed out during ${phase}`);
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            console.error(`  Tab cleanup failed during ${phase}: ${msg}`);
            await this.restartBrowser(`tab cleanup failed during ${phase}`);
        }
    }
    async restartBrowser(reason) {
        console.warn(`  Restarting Chrome profile (${reason})...`);
        await this.browser.restart();
        console.warn("  Chrome profile restarted.");
    }
    /**
     * Idle-close path: tear Chrome down but keep workspace ownership so the
     * next browser action relaunches in-place. Caller (watchdog) holds the
     * browser lock.
     */
    async closeBrowserIdle(reason) {
        console.log(`  Closing idle Chrome (${reason})...`);
        await this.browser.closeKeepLock();
        console.log("  Chrome closed; will relaunch on next browser action.");
    }
    isBrowserLockIdle() {
        return this.browserLock.isIdle();
    }
    tryAcquireBrowserLock() {
        return this.browserLock.tryAcquire();
    }
    /**
     * Acquire a serialized lock for browser access.
     * All operations that may use the browser (chat, scheduled tasks) must go through this
     * to prevent concurrent browser manipulation.
     */
    acquireBrowserLock() {
        return this.browserLock.acquire();
    }
}
function withTimeout(promise, timeoutMs, message) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
        promise
            .then((value) => {
            clearTimeout(timer);
            resolve(value);
        })
            .catch((err) => {
            clearTimeout(timer);
            reject(err);
        });
    });
}
//# sourceMappingURL=agent.js.map