import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve as external_node_path_resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { ReportActionDump, runConnectivityTest } from "@midscene/core";
import { getTmpDir, sleep } from "@midscene/core/utils";
import { PLAYGROUND_SERVER_PORT } from "@midscene/shared/constants";
import { globalModelConfigManager, overrideAIConfig } from "@midscene/shared/env";
import { generateElementByPoint } from "@midscene/shared/extractor";
import { compositePointMarkerImg } from "@midscene/shared/img";
import { getDebug } from "@midscene/shared/logger";
import { uuid as utils_uuid } from "@midscene/shared/utils";
import express from "express";
import { executeAction, formatErrorMessage } from "./common.mjs";
import { MjpegStreamHandler } from "./mjpeg-stream-handler.mjs";
import { PointerInputError, dispatchPointer } from "./pointer-dispatch.mjs";
import { buildRuntimeInfo } from "./runtime-metadata.mjs";
import "dotenv/config";
function _define_property(obj, key, value) {
    if (key in obj) Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
    });
    else obj[key] = value;
    return obj;
}
const defaultPort = PLAYGROUND_SERVER_PORT;
const RECORDER_CAPTURE_AFTER_INTERACT_DELAY_MS = 250;
function serializeAiConfigSignature(aiConfig) {
    return JSON.stringify(Object.entries(aiConfig).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)));
}
function serializeZodField(field) {
    if (!field || 'object' != typeof field) return field;
    const def = field._def;
    if (!def || 'object' != typeof def) return field;
    const typeName = def.typeName;
    const result = {
        _def: {
            typeName
        }
    };
    if (def.description) result._def.description = def.description;
    if (def.innerType) result._def.innerType = serializeZodField(def.innerType);
    if ('ZodDefault' === typeName && 'function' == typeof def.defaultValue) try {
        result._def._serializedDefaultValue = def.defaultValue();
    } catch  {}
    if ('ZodEnum' === typeName && Array.isArray(def.values)) result._def.values = def.values;
    if ('ZodObject' === typeName) {
        const rawShape = 'function' == typeof def.shape ? def.shape() : def.shape;
        if (rawShape && 'object' == typeof rawShape) {
            const serializedShape = {};
            for (const [k, v] of Object.entries(rawShape))serializedShape[k] = serializeZodField(v);
            result._def.shape = serializedShape;
            result.shape = serializedShape;
        }
    }
    if (field.description) result.description = field.description;
    return result;
}
const server_filename = fileURLToPath(import.meta.url);
const server_dirname = dirname(server_filename);
const STATIC_PATH = join(server_dirname, '..', '..', 'static');
const debugScreenshot = getDebug('playground:screenshot', {
    console: true
});
const debugMjpeg = getDebug('playground:mjpeg', {
    console: true
});
const debugInteract = getDebug('playground:interact', {
    console: true
});
class InteractParamsValidationError extends Error {
    constructor(message){
        super(message);
        this.name = 'InteractParamsValidationError';
    }
}
function requireNumber(value, field) {
    if ('number' != typeof value || Number.isNaN(value)) throw new InteractParamsValidationError(`${field} must be a number for this action`);
    return value;
}
function locateFromPoint(x, y, fieldX, fieldY, description) {
    return generateElementByPoint([
        Math.round(requireNumber(x, fieldX)),
        Math.round(requireNumber(y, fieldY))
    ], description);
}
const POINTER_INTERACT_ACTIONS = new Set([
    'Tap',
    'DoubleClick',
    'LongPress',
    'Swipe',
    'DragAndDrop',
    'Scroll',
    'KeyboardPress',
    'Input',
    'Pinch'
]);
function isPointerInteractActionType(actionType) {
    return POINTER_INTERACT_ACTIONS.has(actionType);
}
function summarizeInteractPayload(body) {
    return {
        actionType: body.actionType,
        x: body.x,
        y: body.y,
        endX: body.endX,
        endY: body.endY,
        duration: body.duration,
        direction: body.direction,
        scrollType: body.scrollType,
        distance: body.distance,
        keyName: body.keyName,
        valueLength: 'string' == typeof body.value ? body.value.length : void 0
    };
}
const buildLocateActionParams = (body, actionType)=>{
    const params = {
        locate: locateFromPoint(body.x, body.y, 'x', 'y', `manual ${actionType}`)
    };
    if ('number' == typeof body.duration) params.duration = body.duration;
    return params;
};
const buildSwipeParams = (body)=>{
    const params = {
        start: locateFromPoint(body.x, body.y, 'x', 'y', 'manual swipe start'),
        end: locateFromPoint(body.endX, body.endY, 'endX', 'endY', 'manual swipe end')
    };
    if ('number' == typeof body.duration) params.duration = body.duration;
    if ('number' == typeof body.repeat) params.repeat = body.repeat;
    return params;
};
const buildDragAndDropParams = (body)=>({
        from: locateFromPoint(body.x, body.y, 'x', 'y', 'manual drag from'),
        to: locateFromPoint(body.endX, body.endY, 'endX', 'endY', 'manual drag to')
    });
const buildScrollParams = (body)=>{
    const params = {
        scrollType: 'string' == typeof body.scrollType ? body.scrollType : 'singleAction'
    };
    if ('string' == typeof body.direction) params.direction = body.direction;
    if ('number' == typeof body.distance) params.distance = body.distance;
    if ('number' == typeof body.x && 'number' == typeof body.y) params.locate = locateFromPoint(body.x, body.y, 'x', 'y', 'manual scroll');
    return params;
};
const buildKeyboardPressParams = (body)=>{
    if ('string' != typeof body.keyName) throw new InteractParamsValidationError('keyName is required for KeyboardPress');
    const params = {
        keyName: body.keyName
    };
    if ('number' == typeof body.x && 'number' == typeof body.y) params.locate = locateFromPoint(body.x, body.y, 'x', 'y', 'manual keyboard press');
    return params;
};
const buildInputParams = (body)=>{
    if ('string' != typeof body.value) throw new InteractParamsValidationError('value is required for Input');
    const params = {
        value: body.value
    };
    if ('number' == typeof body.x && 'number' == typeof body.y) params.locate = locateFromPoint(body.x, body.y, 'x', 'y', 'manual input');
    if ('string' == typeof body.mode) params.mode = body.mode;
    if ('boolean' == typeof body.autoDismissKeyboard) params.autoDismissKeyboard = body.autoDismissKeyboard;
    return params;
};
function getManualInteractParamBuilder(actionType) {
    switch(actionType){
        case 'Tap':
        case 'DoubleClick':
        case 'RightClick':
        case 'Hover':
        case 'LongPress':
            return buildLocateActionParams;
        case 'Swipe':
            return buildSwipeParams;
        case 'DragAndDrop':
            return buildDragAndDropParams;
        case 'Scroll':
            return buildScrollParams;
        case 'KeyboardPress':
            return buildKeyboardPressParams;
        case 'Input':
            return buildInputParams;
        default:
            return;
    }
}
function buildInteractParams(actionType, body) {
    const builder = getManualInteractParamBuilder(actionType);
    if (builder) return builder(body, actionType);
    const { actionType: _omit, ...passthrough } = body;
    return passthrough;
}
function createManualExecutorContext(actionType, param) {
    const task = {
        type: 'Action Space',
        subType: actionType,
        param,
        executor: async ()=>void 0,
        taskId: `manual-${utils_uuid()}`,
        status: 'running'
    };
    return {
        task
    };
}
const errorHandler = (err, req, res, next)=>{
    console.error(err);
    const errorMessage = err instanceof Error ? err.message : 'Internal server error';
    res.status(500).json({
        error: errorMessage
    });
};
const RECOVERABLE_PAGE_SESSION_ERROR_PATTERN = /Session closed|page has been closed|target closed|browser has been closed|Target page, context or browser has been closed/i;
const BROWSER_CHROME_NAVIGATION_ACTIONS = new Set([
    'GoBack',
    'GoForward',
    'Reload',
    'Stop'
]);
function isRecoverablePageSessionError(error) {
    const message = error instanceof Error ? error.message : String(error);
    return RECOVERABLE_PAGE_SESSION_ERROR_PATTERN.test(message);
}
class PlaygroundServer {
    setActiveAgent(agent, options = {}) {
        this._activeConnection.agent = agent;
        if (!options.preserveActiveStream) this._mjpegHandler.reset();
    }
    get agent() {
        return this._activeConnection.agent;
    }
    assertNoActiveSessionForBaseStateUpdate(methodName) {
        if (this._activeConnection.session) throw new Error(`${methodName} cannot update prepared state while a session is active`);
    }
    buildBaseRuntimeState() {
        if (!this._baseRuntimeState) return;
        return {
            ...this._baseRuntimeState,
            metadata: this.buildSessionMetadata()
        };
    }
    resetConnectionToBaseState() {
        this._activeConnection = {
            session: null,
            agent: this._activeConnection.agent,
            agentFactory: this._activeConnection.agentFactory,
            runtime: this.buildBaseRuntimeState(),
            executionHooks: this._baseExecutionHooks,
            sidecars: this._baseSidecars
        };
    }
    syncRuntimeState() {
        this._baseRuntimeState = {
            ...this._baseRuntimeState || {},
            metadata: this.buildSessionMetadata()
        };
        if (this._activeConnection.session) {
            this._activeConnection = {
                ...this._activeConnection,
                runtime: this._activeConnection.runtime ? {
                    ...this._activeConnection.runtime,
                    metadata: this.buildSessionMetadata()
                } : this.buildBaseRuntimeState()
            };
            return;
        }
        this.resetConnectionToBaseState();
    }
    restoreBaseSessionState() {
        this.taskExecutionDumps = {};
        this.currentTaskId = null;
        this.sessionSetupState = 'blocked' === this.sessionSetupState ? 'blocked' : 'required';
        this._activeConnection = {
            session: null,
            agent: null,
            agentFactory: null,
            runtime: this.buildBaseRuntimeState(),
            executionHooks: this._baseExecutionHooks,
            sidecars: this._baseSidecars
        };
        this._mjpegHandler.reset();
        this.syncRuntimeState();
    }
    setPreparedPlatform(prepared) {
        if (this._activeConnection.session && this._activeConnection.agentFactory) this._activeConnection.session = null;
        this.assertNoActiveSessionForBaseStateUpdate('setPreparedPlatform');
        this.sessionManager = prepared.sessionManager;
        this._basePreparedMetadata = prepared.metadata ? {
            ...prepared.metadata
        } : void 0;
        this._baseRuntimeState = {
            platformId: prepared.platformId,
            title: prepared.title,
            description: prepared.description,
            preview: prepared.preview,
            metadata: this.buildSessionMetadata()
        };
        this._baseExecutionHooks = prepared.executionHooks;
        this._baseSidecars = prepared.sidecars;
        this.resetConnectionToBaseState();
        if (this.sessionManager && !this._activeConnection.agent && !this._activeConnection.session) {
            this.sessionSetupState = this._basePreparedMetadata?.setupState === 'blocked' ? 'blocked' : 'required';
            this.sessionSetupBlockingReason = 'string' == typeof this._basePreparedMetadata?.setupBlockingReason ? this._basePreparedMetadata.setupBlockingReason : void 0;
        }
    }
    setPreviewDescriptor(preview) {
        this.assertNoActiveSessionForBaseStateUpdate("setPreviewDescriptor");
        this._baseRuntimeState = {
            ...this._baseRuntimeState || {},
            preview
        };
        this.resetConnectionToBaseState();
    }
    setRuntimeMetadata(metadata) {
        this.assertNoActiveSessionForBaseStateUpdate('setRuntimeMetadata');
        this._basePreparedMetadata = metadata ? {
            ...metadata
        } : void 0;
        this.syncRuntimeState();
    }
    getRuntimeInfo() {
        return buildRuntimeInfo({
            platformId: this._activeConnection.runtime?.platformId,
            title: this._activeConnection.runtime?.title,
            platformDescription: this._activeConnection.runtime?.description,
            interfaceType: this._activeConnection.agent?.interface?.interfaceType || 'Unknown',
            interfaceDescription: this._activeConnection.agent?.interface?.describe?.() || void 0,
            preview: this._activeConnection.runtime?.preview,
            metadata: this.buildSessionMetadata(),
            supportsScreenshot: 'function' == typeof this._activeConnection.agent?.interface?.screenshotBase64,
            mjpegStreamUrl: this._activeConnection.agent?.interface?.mjpegStreamUrl,
            scrcpyPort: this.scrcpyPort
        });
    }
    isEffectivelyConnected() {
        const rawConnected = this.sessionManager ? Boolean(this._activeConnection.session?.connected && this._activeConnection.agent) : Boolean(this._activeConnection.agent);
        return rawConnected || !this._agentReady;
    }
    getSessionInfo() {
        return {
            connected: this.isEffectivelyConnected(),
            displayName: this._activeConnection.session?.displayName,
            metadata: {
                ...this._activeConnection.session?.metadata || {}
            },
            setupState: this.sessionSetupState,
            setupBlockingReason: this.sessionSetupBlockingReason
        };
    }
    buildSessionMetadata() {
        return {
            ...this._basePreparedMetadata || {},
            ...this._activeConnection.session?.metadata || {},
            sessionConnected: this.isEffectivelyConnected(),
            sessionDisplayName: this._activeConnection.session?.displayName,
            setupState: this.sessionSetupState,
            ...this.sessionSetupBlockingReason ? {
                setupBlockingReason: this.sessionSetupBlockingReason
            } : {}
        };
    }
    async startSidecars(sidecars) {
        for (const sidecar of sidecars || [])await sidecar.start();
    }
    async stopSidecars(sidecars) {
        for (const sidecar of sidecars || [])await sidecar.stop?.();
    }
    getActiveAgentOrThrow() {
        if (!this._activeConnection.agent) throw new Error('No active session');
        return this._activeConnection.agent;
    }
    async getRecorderCapabilities() {
        const agent = this._activeConnection.agent;
        const platformId = this._activeConnection.runtime?.platformId || agent?.interface?.interfaceType;
        if (!agent) return {
            supported: false,
            source: 'unsupported',
            platformId,
            error: 'No active session.'
        };
        if (this.canRecordStudioPreviewInteractions()) return {
            supported: true,
            source: 'studio-preview',
            platformId
        };
        return {
            supported: false,
            source: 'unsupported',
            platformId,
            error: `Preview recording is unavailable for ${platformId || 'the current target'} because it does not expose manual interaction controls.`
        };
    }
    resetRecorderState() {
        this._recorderSessionId = null;
        this._recorderEvents = [];
        this._studioPreviewRecorderLastScreenshot = void 0;
        this._studioPreviewRecorderLastPageState = void 0;
    }
    canRecordStudioPreviewInteractions() {
        const agent = this._activeConnection.agent;
        if (!agent) return false;
        if (agent.interface.inputPrimitives) return true;
        try {
            return 'function' == typeof agent.interface.actionSpace && agent.interface.actionSpace().length > 0;
        } catch  {
            return false;
        }
    }
    async takeRecorderScreenshot() {
        const agent = this._activeConnection.agent;
        if ('function' != typeof agent?.interface?.screenshotBase64) return;
        try {
            return await agent.interface.screenshotBase64();
        } catch (error) {
            debugScreenshot('recorder screenshot failed:', error);
            return;
        }
    }
    async getActivePageInfo() {
        const agent = this._activeConnection.agent;
        if ('function' != typeof agent?.interface?.size) return {
            width: 0,
            height: 0
        };
        try {
            return await agent.interface.size();
        } catch (error) {
            debugScreenshot('recorder page size failed:', error);
            return {
                width: 0,
                height: 0
            };
        }
    }
    async getActivePageUrl() {
        const activeInterface = this._activeConnection.agent?.interface;
        const getUrl = activeInterface?.url;
        if ('function' != typeof getUrl) return;
        try {
            const url = await getUrl.call(activeInterface);
            return 'string' == typeof url && url ? url : void 0;
        } catch (error) {
            debugScreenshot('recorder page url failed:', error);
            return;
        }
    }
    async getActivePageTitle() {
        const activeInterface = this._activeConnection.agent?.interface;
        const evaluateJavaScript = activeInterface?.evaluateJavaScript;
        if ('function' != typeof evaluateJavaScript) return;
        try {
            const title = await evaluateJavaScript.call(activeInterface, 'document.title');
            return 'string' == typeof title && title ? title : void 0;
        } catch (error) {
            debugScreenshot('recorder page title failed:', error);
            return;
        }
    }
    async getActiveRecorderPageState() {
        const [pageInfo, url, title] = await Promise.all([
            this.getActivePageInfo(),
            this.getActivePageUrl(),
            this.getActivePageTitle()
        ]);
        return {
            pageInfo,
            ...url ? {
                url
            } : {},
            ...title ? {
                title
            } : {}
        };
    }
    async captureRecorderSnapshotBeforeInteract() {
        if (!this._recorderSessionId) return;
        return {
            screenshot: this._studioPreviewRecorderLastScreenshot,
            pageState: this._studioPreviewRecorderLastPageState || await this.getActiveRecorderPageState()
        };
    }
    async startStudioPreviewRecorder(sessionId) {
        this._recorderSessionId = sessionId;
        this._studioPreviewRecorderLastScreenshot = await this.takeRecorderScreenshot();
        const initialPageState = await this.getActiveRecorderPageState();
        this._studioPreviewRecorderLastPageState = initialPageState;
        const initialNavigationEvent = this.buildStudioPreviewInitialNavigationEvent(initialPageState, this._studioPreviewRecorderLastScreenshot);
        if (initialNavigationEvent) this._recorderEvents.push(initialNavigationEvent);
    }
    async storeStudioPreviewRecorderEvent(payload, snapshotBefore) {
        if (!this._recorderSessionId) return;
        const before = snapshotBefore || await this.captureRecorderSnapshotBeforeInteract();
        const screenshotBefore = before?.screenshot;
        debugInteract('recorder capture scheduled after action %o', {
            payload: summarizeInteractPayload(payload),
            delayMs: RECORDER_CAPTURE_AFTER_INTERACT_DELAY_MS,
            hasBeforeScreenshot: Boolean(screenshotBefore),
            beforeUrl: before?.pageState.url
        });
        await sleep(RECORDER_CAPTURE_AFTER_INTERACT_DELAY_MS);
        if (!this._recorderSessionId) return;
        const screenshotAfter = await this.takeRecorderScreenshot();
        const pageStateAfter = await this.getActiveRecorderPageState();
        debugInteract('recorder capture completed after action %o', {
            payload: summarizeInteractPayload(payload),
            hasAfterScreenshot: Boolean(screenshotAfter),
            afterUrl: pageStateAfter.url
        });
        const event = await this.buildStudioPreviewRecorderEvent(payload, before?.pageState || pageStateAfter, screenshotBefore, screenshotAfter);
        if (!event) {
            this._studioPreviewRecorderLastScreenshot = screenshotAfter;
            this._studioPreviewRecorderLastPageState = pageStateAfter;
            return;
        }
        this._recorderEvents.push(event);
        const navigationEvent = this.buildStudioPreviewNavigationChangeEvent(payload, before?.pageState, pageStateAfter, screenshotAfter);
        if (navigationEvent) this._recorderEvents.push(navigationEvent);
        this._studioPreviewRecorderLastScreenshot = screenshotAfter;
        this._studioPreviewRecorderLastPageState = pageStateAfter;
    }
    async buildStudioPreviewRecorderEvent(payload, pageState, screenshotBefore, screenshotAfter) {
        const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
        if (!actionType) return null;
        const { pageInfo, url, title } = pageState;
        const timestamp = Date.now();
        const x = 'number' == typeof payload.x ? payload.x : void 0;
        const y = 'number' == typeof payload.y ? payload.y : void 0;
        const endX = 'number' == typeof payload.endX ? payload.endX : void 0;
        const endY = 'number' == typeof payload.endY ? payload.endY : void 0;
        const dragDescription = void 0 !== x && void 0 !== y && void 0 !== endX && void 0 !== endY ? `${Math.round(x)},${Math.round(y)} -> ${Math.round(endX)},${Math.round(endY)}` : void 0;
        const screenshotWithMarker = void 0 !== x && void 0 !== y ? await this.createRecorderScreenshotWithMarker(screenshotBefore || screenshotAfter, pageInfo, {
            x,
            y
        }) : void 0;
        const base = {
            source: 'studio-preview',
            actionType,
            rawPayload: payload,
            pageInfo,
            url,
            title,
            screenshotBefore,
            screenshotAfter,
            screenshotWithBox: screenshotWithMarker,
            descriptionLoading: true,
            descriptionSource: void 0,
            timestamp,
            hashId: `studio-preview-${actionType}-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
        };
        switch(actionType){
            case 'Tap':
            case 'DoubleClick':
            case 'LongPress':
            case 'RightClick':
                return {
                    ...base,
                    type: 'click',
                    elementRect: void 0 !== x && void 0 !== y ? {
                        x,
                        y,
                        left: x,
                        top: y
                    } : {},
                    value: void 0 !== x && void 0 !== y ? `${Math.round(x)},${Math.round(y)}` : void 0
                };
            case 'DragAndDrop':
            case 'Swipe':
                return {
                    ...base,
                    type: 'drag',
                    elementRect: void 0 !== x && void 0 !== y ? {
                        x,
                        y,
                        left: x,
                        top: y,
                        width: void 0 !== endX ? Math.abs(endX - x) || void 0 : void 0,
                        height: void 0 !== endY ? Math.abs(endY - y) || void 0 : void 0
                    } : {},
                    value: dragDescription
                };
            case 'Input':
                return {
                    ...base,
                    type: 'input',
                    value: 'string' == typeof payload.value ? payload.value : '',
                    elementRect: void 0 !== x && void 0 !== y ? {
                        x,
                        y,
                        left: x,
                        top: y
                    } : {}
                };
            case 'KeyboardPress':
                return {
                    ...base,
                    type: 'keydown',
                    value: 'string' == typeof payload.keyName ? payload.keyName : '',
                    elementRect: void 0 !== x && void 0 !== y ? {
                        x,
                        y,
                        left: x,
                        top: y
                    } : {}
                };
            case 'Scroll':
                return {
                    ...base,
                    type: 'scroll',
                    value: [
                        'string' == typeof payload.direction ? payload.direction : 'down',
                        'number' == typeof payload.distance ? payload.distance : void 0
                    ].filter((part)=>void 0 !== part).join(' '),
                    elementRect: void 0 !== x && void 0 !== y ? {
                        x,
                        y,
                        left: x,
                        top: y
                    } : {}
                };
            case 'GoBack':
            case 'GoForward':
            case 'Reload':
            case 'Stop':
                return {
                    ...base,
                    type: 'navigation',
                    value: actionType,
                    elementDescription: url || actionType,
                    replayInstruction: 'Stop' === actionType ? 'Stop loading the current page.' : url ? `Wait for navigation to complete at \`${url}\`.` : `${actionType} in the browser.`,
                    actionSummary: 'Stop' === actionType ? 'Stop page loading' : url ? `${actionType} to ${url}` : actionType,
                    semanticConfidence: url ? 'high' : 'medium',
                    descriptionLoading: false,
                    descriptionSource: 'fallback'
                };
            default:
                return {
                    ...base,
                    type: 'click',
                    value: void 0 !== x && void 0 !== y ? `${Math.round(x)},${Math.round(y)}` : actionType
                };
        }
    }
    buildStudioPreviewNavigationChangeEvent(payload, pageStateBefore, pageStateAfter, screenshotAfter) {
        const actionType = 'string' == typeof payload.actionType ? payload.actionType : void 0;
        if (!actionType || BROWSER_CHROME_NAVIGATION_ACTIONS.has(actionType)) return null;
        const beforeUrl = pageStateBefore?.url;
        const afterUrl = pageStateAfter.url;
        if (!afterUrl || beforeUrl === afterUrl) return null;
        const timestamp = Date.now();
        return {
            source: 'studio-preview',
            type: 'navigation',
            actionType: 'NavigationChanged',
            rawPayload: {
                triggerActionType: actionType,
                beforeUrl,
                afterUrl
            },
            pageInfo: pageStateAfter.pageInfo,
            url: afterUrl,
            title: pageStateAfter.title,
            value: afterUrl,
            screenshotBefore: screenshotAfter,
            screenshotAfter,
            elementDescription: afterUrl,
            replayInstruction: `Wait for navigation to complete at \`${afterUrl}\`.`,
            actionSummary: `Wait for navigation to complete at ${afterUrl}`,
            semanticConfidence: 'high',
            descriptionLoading: false,
            descriptionSource: 'fallback',
            timestamp,
            hashId: `studio-preview-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
        };
    }
    buildStudioPreviewInitialNavigationEvent(pageState, screenshot) {
        if (!pageState.url) return null;
        const timestamp = Date.now();
        return {
            source: 'studio-preview',
            type: 'navigation',
            actionType: 'InitialNavigation',
            rawPayload: {
                url: pageState.url,
                title: pageState.title
            },
            pageInfo: pageState.pageInfo,
            url: pageState.url,
            title: pageState.title,
            value: pageState.url,
            screenshotBefore: screenshot,
            screenshotAfter: screenshot,
            elementDescription: pageState.url,
            replayInstruction: `Navigate to \`${pageState.url}\`.`,
            actionSummary: `Navigate to ${pageState.url}`,
            semanticConfidence: 'high',
            descriptionLoading: false,
            descriptionSource: 'fallback',
            timestamp,
            hashId: `studio-preview-initial-navigation-${timestamp}-${Math.random().toString(36).slice(2, 8)}`
        };
    }
    async createRecorderScreenshotWithMarker(screenshot, pageInfo, point) {
        if (!screenshot || !pageInfo.width || !pageInfo.height) return;
        try {
            return await compositePointMarkerImg({
                inputImgBase64: screenshot,
                size: pageInfo,
                point
            });
        } catch (error) {
            debugScreenshot('recorder screenshot marker failed:', error);
            return;
        }
    }
    async destroyCurrentAgent({ preserveActiveStream = false } = {}) {
        if (!this._activeConnection.agent) return;
        try {
            this.resetRecorderState();
            if ('function' == typeof this._activeConnection.agent.destroy) await this._activeConnection.agent.destroy();
        } catch (error) {
            console.warn('Failed to destroy old agent:', error);
        } finally{
            this.setActiveAgent(null, {
                preserveActiveStream
            });
            this._configDirty = false;
        }
    }
    async destroyCurrentSession() {
        const previousSession = this._activeConnection.session;
        const previousSidecars = this._activeConnection.sidecars;
        await this.destroyCurrentAgent();
        await this.stopSidecars(previousSidecars);
        if (this.sessionManager?.destroySession) await this.sessionManager.destroySession(previousSession || void 0);
        this.restoreBaseSessionState();
    }
    async applyCreatedSession(session) {
        if (!session.agent && !session.agentFactory) throw new Error('Session creation must provide either an agent or agentFactory');
        const sessionSidecars = session.sidecars || this._baseSidecars;
        await this.startSidecars(sessionSidecars);
        try {
            this._activeConnection = {
                session: {
                    connected: true,
                    displayName: session.displayName,
                    metadata: session.metadata ? {
                        ...session.metadata
                    } : {}
                },
                agent: session.agent || null,
                agentFactory: session.agentFactory || null,
                runtime: {
                    platformId: session.platformId ?? this._baseRuntimeState?.platformId,
                    title: session.title ?? this._baseRuntimeState?.title,
                    description: session.platformDescription ?? this._baseRuntimeState?.description,
                    preview: session.preview ?? this._baseRuntimeState?.preview,
                    metadata: session.metadata ? {
                        ...session.metadata
                    } : {}
                },
                executionHooks: session.executionHooks || this._baseExecutionHooks,
                sidecars: sessionSidecars
            };
            this._mjpegHandler.reset();
            this.sessionSetupState = 'ready';
            this.sessionSetupBlockingReason = void 0;
            this.syncRuntimeState();
        } catch (error) {
            await this.stopSidecars(sessionSidecars).catch(()=>{});
            this.restoreBaseSessionState();
            throw error;
        }
    }
    async getSessionSetupSchema(input) {
        if (!this.sessionManager) return null;
        return this.sessionManager.getSetupSchema ? this.sessionManager.getSetupSchema(input) : null;
    }
    async getSessionTargets() {
        if (!this.sessionManager?.listTargets) return [];
        return this.sessionManager.listTargets();
    }
    get app() {
        return this._app;
    }
    initializeApp() {
        if (this._initialized) return;
        this._app.use(express.json({
            limit: '50mb'
        }));
        this._app.use((req, _res, next)=>{
            const { context } = req.body || {};
            if (this._activeConnection.agent && context && 'updateContext' in this._activeConnection.agent.interface && 'function' == typeof this._activeConnection.agent.interface.updateContext) {
                this._activeConnection.agent.interface.updateContext(context);
                console.log('Context updated by PlaygroundServer middleware');
            }
            next();
        });
        this.setupRoutes();
        this.setupStaticRoutes();
        this._app.use(errorHandler);
        this._initialized = true;
    }
    filePathForUuid(uuid) {
        if (!/^[a-zA-Z0-9-]+$/.test(uuid)) throw new Error('Invalid uuid format');
        const filePath = join(this.tmpDir, `${uuid}.json`);
        const resolvedPath = external_node_path_resolve(filePath);
        const resolvedTmpDir = external_node_path_resolve(this.tmpDir);
        if (!resolvedPath.startsWith(resolvedTmpDir)) throw new Error('Invalid path');
        return filePath;
    }
    saveContextFile(uuid, context) {
        const tmpFile = this.filePathForUuid(uuid);
        console.log(`save context file: ${tmpFile}`);
        writeFileSync(tmpFile, context);
        return tmpFile;
    }
    async recreateAgent({ preserveActiveStream = false } = {}) {
        this._agentReady = false;
        console.log('Recreating agent to cancel current task...');
        await this.destroyCurrentAgent({
            preserveActiveStream
        });
        if (this._activeConnection.agentFactory) try {
            this.setActiveAgent(await this._activeConnection.agentFactory(), {
                preserveActiveStream
            });
            this._agentReady = true;
            console.log('Agent recreated successfully');
        } catch (error) {
            this._agentReady = true;
            console.error('Failed to recreate agent:', error);
            throw error;
        }
        else {
            this._agentReady = true;
            console.warn('Agent destroyed but cannot recreate: no factory function provided. Next /execute call will fail.');
        }
    }
    async recoverActiveAgentAfterPreviewError(error, reason) {
        if (!this._activeConnection.agentFactory || !isRecoverablePageSessionError(error)) return null;
        debugMjpeg(`Recovering active agent after ${reason}:`, error);
        try {
            this._mjpegHandler.reset();
            await this.recreateAgent();
            return this._activeConnection.agent;
        } catch (recreateError) {
            debugMjpeg(`Failed to recover active agent after ${reason}:`, recreateError);
            return null;
        }
    }
    findInteractAction(agent, actionType) {
        return agent.interface.actionSpace().find((entry)=>entry.name === actionType);
    }
    canRunBrowserChromeInteractAction(agent, actionType) {
        return 'Stop' === actionType && 'function' == typeof agent.interface.stopLoading;
    }
    async runBrowserChromeInteractAction(agent, actionType) {
        switch(actionType){
            case 'Stop':
                await agent.interface.stopLoading?.();
                return;
        }
    }
    async runInteractAction(agent, actionType, params) {
        if (this.canRunBrowserChromeInteractAction(agent, actionType)) return void await this.runBrowserChromeInteractAction(agent, actionType);
        const action = this.findInteractAction(agent, actionType);
        if (!action || 'function' != typeof action.call) throw new Error(`Action "${actionType}" is not available on the current device`);
        await action.call(params, createManualExecutorContext(actionType, params));
    }
    setupRoutes() {
        this._app.get('/status', async (req, res)=>{
            res.send({
                status: 'ok',
                id: this.id
            });
        });
        this._app.get('/session', async (_req, res)=>{
            res.json(this.getSessionInfo());
        });
        this._app.get('/session/setup', async (req, res)=>{
            try {
                const setup = await this.getSessionSetupSchema(Object.fromEntries(Object.entries(req.query).filter(([, value])=>'string' == typeof value)));
                if (!setup) return res.status(404).json({
                    error: 'Session setup is not available for this playground'
                });
                const targets = await this.getSessionTargets();
                res.json({
                    ...setup,
                    targets: targets.length > 0 ? targets : setup.targets
                });
            } catch (error) {
                res.status(500).json({
                    error: error instanceof Error ? error.message : 'Failed to load session setup'
                });
            }
        });
        this._app.get('/session/targets', async (_req, res)=>{
            try {
                res.json(await this.getSessionTargets());
            } catch (error) {
                res.status(500).json({
                    error: error instanceof Error ? error.message : 'Failed to load session targets'
                });
            }
        });
        this._app.post('/session', async (req, res)=>{
            if (!this.sessionManager) return res.status(404).json({
                error: 'Session creation is not available for this playground'
            });
            if (this.currentTaskId) return res.status(409).json({
                error: 'Cannot replace session while a task is running'
            });
            try {
                await this.destroyCurrentSession();
                const created = await this.sessionManager.createSession(req.body || {});
                await this.applyCreatedSession(created);
                if (!this._activeConnection.agent && this._activeConnection.agentFactory) this.setActiveAgent(await this._activeConnection.agentFactory());
                if (this._configDirty && this._activeConnection.agentFactory) {
                    this._configDirty = false;
                    await this.recreateAgent();
                }
                res.json({
                    session: this.getSessionInfo(),
                    runtimeInfo: this.getRuntimeInfo()
                });
            } catch (error) {
                const failedSessionSidecars = this._activeConnection.session ? this._activeConnection.sidecars : void 0;
                await this.destroyCurrentAgent();
                await this.stopSidecars(failedSessionSidecars).catch(()=>{});
                this.restoreBaseSessionState();
                res.status(400).json({
                    error: error instanceof Error ? error.message : 'Failed to create session'
                });
            }
        });
        this._app.delete('/session', async (_req, res)=>{
            if (this.currentTaskId) return res.status(409).json({
                error: 'Cannot destroy session while a task is running'
            });
            try {
                await this.destroyCurrentSession();
                res.json({
                    session: this.getSessionInfo(),
                    runtimeInfo: this.getRuntimeInfo()
                });
            } catch (error) {
                res.status(500).json({
                    error: error instanceof Error ? error.message : 'Failed to destroy session'
                });
            }
        });
        this._app.get('/context/:uuid', async (req, res)=>{
            const { uuid } = req.params;
            let contextFile;
            try {
                contextFile = this.filePathForUuid(uuid);
            } catch  {
                return res.status(400).json({
                    error: 'Invalid uuid format'
                });
            }
            if (!existsSync(contextFile)) return res.status(404).json({
                error: 'Context not found'
            });
            const context = readFileSync(contextFile, 'utf8');
            res.json({
                context
            });
        });
        this._app.get('/task-progress/:requestId', async (req, res)=>{
            const { requestId } = req.params;
            const executionDump = this.taskExecutionDumps[requestId] || null;
            res.json({
                executionDump
            });
        });
        this._app.post('/action-space', async (req, res)=>{
            try {
                const agent = this.getActiveAgentOrThrow();
                let actionSpace = [];
                actionSpace = agent.interface.actionSpace();
                const processedActionSpace = actionSpace.map((action)=>{
                    if (action && 'object' == typeof action && 'paramSchema' in action) {
                        const typedAction = action;
                        if (typedAction.paramSchema && 'object' == typeof typedAction.paramSchema) {
                            let processedSchema = null;
                            try {
                                if (typedAction.paramSchema.shape && 'object' == typeof typedAction.paramSchema.shape) {
                                    const rawShape = typedAction.paramSchema.shape;
                                    const serializedShape = {};
                                    for (const [key, field] of Object.entries(rawShape))serializedShape[key] = serializeZodField(field);
                                    processedSchema = {
                                        type: 'ZodObject',
                                        shape: serializedShape
                                    };
                                }
                            } catch (e) {
                                const actionName = 'name' in typedAction && 'string' == typeof typedAction.name ? typedAction.name : 'unknown';
                                console.warn('Failed to process paramSchema for action:', actionName, e);
                            }
                            return {
                                ...typedAction,
                                paramSchema: processedSchema
                            };
                        }
                    }
                    return action;
                });
                res.json(processedActionSpace);
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error('Failed to get action space:', error);
                res.status('No active session' === errorMessage ? 409 : 500).json({
                    error: errorMessage
                });
            }
        });
        this._app.post('/playground-with-context', async (req, res)=>{
            const context = req.body.context;
            if (!context) return res.status(400).json({
                error: 'context is required'
            });
            const requestId = utils_uuid();
            this.saveContextFile(requestId, context);
            return res.json({
                location: `/playground/${requestId}`,
                uuid: requestId
            });
        });
        this._app.post('/execute', async (req, res)=>{
            let agent;
            try {
                agent = this.getActiveAgentOrThrow();
            } catch (error) {
                return res.status(409).json({
                    error: error instanceof Error ? error.message : 'No active session'
                });
            }
            const { type, prompt, params, requestId, deepLocate, deepThink, screenshotIncluded, domIncluded, deviceOptions } = req.body;
            if (!type) return res.status(400).json({
                error: 'type is required'
            });
            if (this._activeConnection.agentFactory && this._configDirty) {
                this._configDirty = false;
                this._agentReady = false;
                console.log('AI config changed, recreating agent...');
                try {
                    await this.destroyCurrentAgent();
                    this.setActiveAgent(await this._activeConnection.agentFactory());
                    agent = this.getActiveAgentOrThrow();
                    this._agentReady = true;
                    console.log('Agent recreated with new config');
                } catch (error) {
                    this._agentReady = true;
                    console.error('Failed to recreate agent:', error);
                    return res.status(500).json({
                        error: `Failed to create agent: ${error instanceof Error ? error.message : 'Unknown error'}`
                    });
                }
            }
            if (deviceOptions) {
                const iface = agent.interface;
                iface.options = {
                    ...iface.options || {},
                    ...deviceOptions
                };
            }
            if (this.currentTaskId) return res.status(409).json({
                error: 'Another task is already running',
                currentTaskId: this.currentTaskId
            });
            if (requestId) {
                this.currentTaskId = requestId;
                this.taskExecutionDumps[requestId] = null;
                agent.onDumpUpdate = (_dump, executionDump)=>{
                    if (executionDump) this.taskExecutionDumps[requestId] = executionDump;
                };
            }
            const response = {
                result: null,
                dump: null,
                error: null,
                reportHTML: null,
                requestId
            };
            const startTime = Date.now();
            try {
                await this._activeConnection.executionHooks?.beforeExecute?.();
                const actionSpace = agent.interface.actionSpace();
                const value = {
                    type,
                    prompt,
                    params
                };
                response.result = await executeAction(agent, type, actionSpace, value, {
                    deepLocate,
                    deepThink,
                    screenshotIncluded,
                    domIncluded,
                    deviceOptions
                });
            } catch (error) {
                response.error = formatErrorMessage(error);
            } finally{
                try {
                    await this._activeConnection.executionHooks?.afterExecute?.();
                } catch (hookError) {
                    console.error('Failed to run execution after hook:', hookError);
                }
            }
            try {
                const dumpString = agent.dumpDataString({
                    inlineScreenshots: true
                });
                if (dumpString) {
                    const groupedDump = ReportActionDump.fromSerializedString(dumpString);
                    response.dump = groupedDump.executions?.[0] || null;
                } else response.dump = null;
                response.reportHTML = agent.reportHTMLString({
                    inlineScreenshots: true
                }) || null;
                agent.writeOutActionDumps();
                agent.resetDump();
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`write out dump failed: requestId: ${requestId}, ${errorMessage}`);
            } finally{}
            res.send(response);
            const timeCost = Date.now() - startTime;
            if (response.error) console.error(`handle request failed after ${timeCost}ms: requestId: ${requestId}, ${response.error}`);
            else console.log(`handle request done after ${timeCost}ms: requestId: ${requestId}`);
            if (requestId) {
                delete this.taskExecutionDumps[requestId];
                if (this.currentTaskId === requestId) this.currentTaskId = null;
            }
        });
        this._app.post('/cancel/:requestId', async (req, res)=>{
            const { requestId } = req.params;
            if (!requestId) return res.status(400).json({
                error: 'requestId is required'
            });
            try {
                const agent = this.getActiveAgentOrThrow();
                if (this.currentTaskId !== requestId) return res.json({
                    status: 'not_found',
                    message: 'Task not found or already completed'
                });
                console.log(`Cancelling task: ${requestId}`);
                let dump = null;
                let reportHTML = null;
                try {
                    const dumpString = agent.dumpDataString?.({
                        inlineScreenshots: true
                    });
                    if (dumpString) {
                        const groupedDump = ReportActionDump.fromSerializedString(dumpString);
                        dump = groupedDump.executions?.[0] || null;
                    }
                    reportHTML = agent.reportHTMLString?.({
                        inlineScreenshots: true
                    }) || null;
                } catch (error) {
                    console.warn('Failed to get execution data before cancel:', error);
                }
                try {
                    await this.recreateAgent({
                        preserveActiveStream: true
                    });
                } catch (error) {
                    console.warn('Failed to recreate agent during cancel:', error);
                }
                delete this.taskExecutionDumps[requestId];
                this.currentTaskId = null;
                res.json({
                    status: 'cancelled',
                    message: 'Task cancelled successfully',
                    dump,
                    reportHTML
                });
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Failed to cancel: ${errorMessage}`);
                res.status('No active session' === errorMessage ? 409 : 500).json({
                    error: `Failed to cancel: ${errorMessage}`
                });
            }
        });
        this._app.post('/recorder/start', async (req, res)=>{
            const { sessionId } = req.body ?? {};
            if ('string' != typeof sessionId || !sessionId.trim()) return res.status(400).json({
                ok: false,
                error: 'sessionId is required'
            });
            const capabilities = await this.getRecorderCapabilities();
            if (!capabilities.supported) {
                this.resetRecorderState();
                return res.json({
                    ok: false,
                    supported: false,
                    source: capabilities.source,
                    platformId: capabilities.platformId,
                    error: capabilities.error
                });
            }
            try {
                this.resetRecorderState();
                await this.startStudioPreviewRecorder(sessionId);
                res.json({
                    ok: true,
                    supported: true,
                    source: 'studio-preview',
                    platformId: capabilities.platformId
                });
            } catch (error) {
                this.resetRecorderState();
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                res.status(500).json({
                    ok: false,
                    supported: false,
                    error: errorMessage
                });
            }
        });
        this._app.get('/recorder/capabilities', async (_req, res)=>{
            res.json(await this.getRecorderCapabilities());
        });
        this._app.post('/recorder/stop', async (_req, res)=>{
            this._recorderSessionId = null;
            this._studioPreviewRecorderLastScreenshot = void 0;
            this._studioPreviewRecorderLastPageState = void 0;
            res.json({
                ok: true
            });
        });
        this._app.get('/recorder/events', async (req, res)=>{
            const since = 'string' == typeof req.query.since ? Number.parseInt(req.query.since, 10) : 0;
            const startIndex = Number.isFinite(since) && since > 0 ? since : 0;
            res.json({
                events: this._recorderEvents.slice(startIndex),
                nextIndex: this._recorderEvents.length
            });
        });
        this._app.get('/screenshot', async (_req, res)=>{
            try {
                let agent = this.getActiveAgentOrThrow();
                if ('function' != typeof agent.interface.screenshotBase64) return res.status(500).json({
                    error: 'Screenshot method not available on current interface'
                });
                let screenshot;
                try {
                    screenshot = await agent.interface.screenshotBase64();
                } catch (error) {
                    const recoveredAgent = await this.recoverActiveAgentAfterPreviewError(error, 'screenshot capture');
                    if (!recoveredAgent || 'function' != typeof recoveredAgent.interface.screenshotBase64) throw error;
                    agent = recoveredAgent;
                    screenshot = await agent.interface.screenshotBase64();
                }
                res.json({
                    screenshot,
                    timestamp: Date.now()
                });
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                const statusCode = 'No active session' === errorMessage ? 409 : 500;
                if (409 !== statusCode) console.error(`Failed to take screenshot: ${errorMessage}`);
                res.status(statusCode).json({
                    error: `Failed to take screenshot: ${errorMessage}`
                });
            }
        });
        this._app.get('/mjpeg', async (req, res)=>{
            const agent = this._activeConnection.agent;
            if (!agent) return res.status(409).json({
                error: 'No active session'
            });
            await this._mjpegHandler.serve(req, res);
        });
        this._app.get('/interface-info', async (_req, res)=>{
            try {
                const runtimeInfo = this.getRuntimeInfo();
                const agent = this._activeConnection.agent;
                let size;
                let navigationState;
                let actionTypes;
                if ('function' == typeof agent?.interface?.size) try {
                    size = await agent.interface.size();
                } catch (error) {
                    debugScreenshot('interface size() failed:', error);
                }
                if ('function' == typeof agent?.interface?.navigationState) try {
                    navigationState = await agent.interface.navigationState();
                } catch (error) {
                    debugScreenshot('interface navigationState() failed:', error);
                }
                if ('function' == typeof agent?.interface?.actionSpace) try {
                    const actions = agent.interface.actionSpace();
                    actionTypes = Array.isArray(actions) ? actions.map((action)=>action?.name).filter((name)=>'string' == typeof name) : void 0;
                } catch (error) {
                    debugScreenshot('interface actionSpace() failed:', error);
                }
                res.json({
                    type: runtimeInfo.interface.type,
                    description: runtimeInfo.interface.description,
                    ...size ? {
                        size
                    } : {},
                    ...navigationState ? {
                        navigationState
                    } : {},
                    ...actionTypes ? {
                        actionTypes
                    } : {}
                });
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Failed to get interface info: ${errorMessage}`);
                res.status(500).json({
                    error: `Failed to get interface info: ${errorMessage}`
                });
            }
        });
        this._app.post('/interact', async (req, res)=>{
            let agent;
            try {
                agent = this.getActiveAgentOrThrow();
            } catch (error) {
                return res.status(409).json({
                    error: error instanceof Error ? error.message : 'No active session'
                });
            }
            const { actionType } = req.body ?? {};
            if ('string' != typeof actionType || !actionType) return res.status(400).json({
                error: 'actionType is required'
            });
            try {
                const interactStartedAt = Date.now();
                debugInteract('received manual interact %o', {
                    payload: summarizeInteractPayload(req.body ?? {}),
                    interfaceType: agent.interface.interfaceType,
                    recorderActive: Boolean(this._recorderSessionId),
                    hasInputPrimitives: Boolean(agent.interface.inputPrimitives)
                });
                const recorderSnapshotBefore = await this.captureRecorderSnapshotBeforeInteract();
                const inputPrimitives = agent.interface.inputPrimitives;
                if (inputPrimitives) {
                    await dispatchPointer(inputPrimitives, req.body ?? {}, ()=>agent.interface.size());
                    debugInteract('primitive manual interact dispatched %o', {
                        payload: summarizeInteractPayload(req.body ?? {}),
                        elapsedMs: Date.now() - interactStartedAt
                    });
                    await this.storeStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore);
                    debugInteract('manual interact completed %o', {
                        payload: summarizeInteractPayload(req.body ?? {}),
                        elapsedMs: Date.now() - interactStartedAt
                    });
                    res.json({});
                    return;
                }
                if (!this.findInteractAction(agent, actionType) && !this.canRunBrowserChromeInteractAction(agent, actionType)) return res.status(404).json({
                    error: isPointerInteractActionType(actionType) ? 'Manual control is not supported on this device' : `Action "${actionType}" is not available on the current device`
                });
                const params = buildInteractParams(actionType, req.body ?? {});
                await this.runInteractAction(agent, actionType, params);
                debugInteract('actionSpace manual interact dispatched %o', {
                    payload: summarizeInteractPayload(req.body ?? {}),
                    elapsedMs: Date.now() - interactStartedAt
                });
                await this.storeStudioPreviewRecorderEvent(req.body ?? {}, recorderSnapshotBefore);
                debugInteract('manual interact completed %o', {
                    payload: summarizeInteractPayload(req.body ?? {}),
                    elapsedMs: Date.now() - interactStartedAt
                });
                res.json({});
            } catch (error) {
                if (error instanceof PointerInputError) return res.status(error.statusCode).json({
                    error: error.message
                });
                if (error instanceof InteractParamsValidationError) return res.status(400).json({
                    error: error.message
                });
                const recoveredAgent = await this.recoverActiveAgentAfterPreviewError(error, `manual interact action "${actionType}"`);
                if (recoveredAgent) return res.status(409).json({
                    error: 'The page session was closed and has been recreated. Please retry the action.'
                });
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Failed to run interact action "${actionType}": ${errorMessage}`);
                res.status(500).json({
                    error: errorMessage
                });
            }
        });
        this._app.get('/runtime-info', async (_req, res)=>{
            try {
                res.json(this.getRuntimeInfo());
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Failed to get runtime info: ${errorMessage}`);
                res.status(500).json({
                    error: `Failed to get runtime info: ${errorMessage}`
                });
            }
        });
        this.app.post('/config', async (req, res)=>{
            const { aiConfig } = req.body;
            if (!aiConfig || 'object' != typeof aiConfig) return res.status(400).json({
                error: 'aiConfig is required and must be an object'
            });
            if (0 === Object.keys(aiConfig).length) return res.json({
                status: 'ok',
                message: 'AI config not changed due to empty object'
            });
            const nextConfigSignature = serializeAiConfigSignature(aiConfig);
            const configChanged = nextConfigSignature !== this._lastAiConfigSignature;
            try {
                if (configChanged) {
                    overrideAIConfig(aiConfig);
                    this._lastAiConfigSignature = nextConfigSignature;
                    this._configDirty = Boolean(this._activeConnection.agent);
                }
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Failed to update AI config: ${errorMessage}`);
                return res.status(500).json({
                    error: `Failed to update AI config: ${errorMessage}`
                });
            }
            if (!configChanged) return res.json({
                status: 'ok',
                message: 'AI config not changed because it is identical to current'
            });
            try {
                globalModelConfigManager.getModelConfig('default');
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`AI config validation failed: ${errorMessage}`);
                return res.status(400).json({
                    error: errorMessage
                });
            }
            return res.json({
                status: 'ok',
                message: this._configDirty ? 'AI config updated. Agent will be recreated on next execution.' : 'AI config updated. New sessions will use it immediately.'
            });
        });
        this.app.post('/connectivity-test', async (_req, res)=>{
            try {
                const result = await runConnectivityTest({
                    defaultModelConfig: globalModelConfigManager.getModelConfig('default'),
                    planningModelConfig: globalModelConfigManager.getModelConfig('planning'),
                    insightModelConfig: globalModelConfigManager.getModelConfig('insight')
                });
                return res.json(result);
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                console.error(`Connectivity test failed: ${errorMessage}`);
                return res.status(500).json({
                    error: errorMessage
                });
            }
        });
    }
    setupStaticRoutes() {
        this._app.get('/', (_req, res)=>{
            this.serveHtmlWithPorts(res);
        });
        this._app.get('/index.html', (_req, res)=>{
            this.serveHtmlWithPorts(res);
        });
        this._app.use(express["static"](this.staticPath));
        this._app.get('*', (_req, res)=>{
            this.serveHtmlWithPorts(res);
        });
    }
    serveHtmlWithPorts(res) {
        try {
            const htmlPath = join(this.staticPath, 'index.html');
            let html = readFileSync(htmlPath, 'utf8');
            const scrcpyPort = this.scrcpyPort ?? this.port + 1;
            const configScript = `
        <script>
          window.SCRCPY_PORT = ${scrcpyPort};
        </script>
      `;
            html = html.replace('</head>', `${configScript}</head>`);
            res.setHeader('Content-Type', 'text/html');
            res.send(html);
        } catch (error) {
            console.error('Error serving HTML with ports:', error);
            res.status(500).send('Internal Server Error');
        }
    }
    async launch(port) {
        if (this._activeConnection.agentFactory && !this.sessionManager) {
            console.log('Initializing agent from factory function...');
            this.setActiveAgent(await this._activeConnection.agentFactory());
            this._activeConnection.session = {
                connected: true,
                metadata: {}
            };
            this.sessionSetupState = 'ready';
            this.syncRuntimeState();
            console.log('Agent initialized successfully');
        }
        this.initializeApp();
        this.port = port || defaultPort;
        return new Promise((resolve)=>{
            const serverPort = this.port ?? defaultPort;
            this.server = this._app.listen(serverPort, '0.0.0.0', ()=>{
                resolve(this);
            });
        });
    }
    async close() {
        await this.destroyCurrentSession().catch((error)=>{
            console.warn('Failed to destroy current session during shutdown:', error);
        });
        this._mjpegHandler.shutdown();
        return new Promise((resolve, reject)=>{
            if (this.server) {
                this.taskExecutionDumps = {};
                this.server.close((error)=>{
                    if (error) reject(error);
                    else {
                        this.server = void 0;
                        resolve();
                    }
                });
            } else resolve();
        });
    }
    constructor(agent, staticPath = STATIC_PATH, id){
        _define_property(this, "_app", void 0);
        _define_property(this, "tmpDir", void 0);
        _define_property(this, "server", void 0);
        _define_property(this, "port", void 0);
        _define_property(this, "staticPath", void 0);
        _define_property(this, "taskExecutionDumps", void 0);
        _define_property(this, "id", void 0);
        _define_property(this, "scrcpyPort", void 0);
        _define_property(this, "_initialized", false);
        _define_property(this, "_mjpegHandler", new MjpegStreamHandler({
            getNativeUrl: ()=>this._activeConnection.agent?.interface?.mjpegStreamUrl,
            getActiveInterface: ()=>this._activeConnection.agent?.interface ?? null,
            takeScreenshot: ()=>this.getActiveAgentOrThrow().interface.screenshotBase64(),
            canTakeScreenshot: ()=>'function' == typeof this._activeConnection.agent?.interface?.screenshotBase64,
            isAgentReady: ()=>this._agentReady,
            recoverFromPreviewError: async (error, reason)=>(await this.recoverActiveAgentAfterPreviewError(error, reason))?.interface ?? null
        }));
        _define_property(this, "sessionManager", void 0);
        _define_property(this, "sessionSetupState", 'ready');
        _define_property(this, "sessionSetupBlockingReason", void 0);
        _define_property(this, "currentTaskId", null);
        _define_property(this, "_agentReady", true);
        _define_property(this, "_configDirty", false);
        _define_property(this, "_lastAiConfigSignature", null);
        _define_property(this, "_baseRuntimeState", void 0);
        _define_property(this, "_basePreparedMetadata", void 0);
        _define_property(this, "_baseExecutionHooks", void 0);
        _define_property(this, "_baseSidecars", void 0);
        _define_property(this, "_recorderSessionId", null);
        _define_property(this, "_recorderEvents", []);
        _define_property(this, "_studioPreviewRecorderLastScreenshot", void 0);
        _define_property(this, "_studioPreviewRecorderLastPageState", void 0);
        _define_property(this, "_activeConnection", {
            session: null,
            agent: null,
            agentFactory: null,
            runtime: void 0,
            executionHooks: void 0,
            sidecars: void 0
        });
        this._app = express();
        this.tmpDir = getTmpDir();
        this.staticPath = staticPath;
        this.taskExecutionDumps = {};
        this.id = id || utils_uuid();
        if ('function' == typeof agent) this._activeConnection.agentFactory = agent;
        else this.setActiveAgent(agent || null);
    }
}
const server = PlaygroundServer;
export { InteractParamsValidationError, PlaygroundServer, buildInteractParams, createManualExecutorContext, server as default, serializeZodField };

//# sourceMappingURL=server.mjs.map