import { AgentSessionRuntime, } from "../../core/agent-session-runtime.js";
import { SessionManager } from "../../core/session-manager.js";
import { RemoteCommandCatalog } from "./remote-command-catalog.js";
import { sleep } from "../../utils/sleep.js";
export class IsolatedInteractiveRuntime extends AgentSessionRuntime {
    constructor(localRuntime, createRuntime, client) {
        super(localRuntime.session, localRuntime.services, createRuntime, [...localRuntime.diagnostics], localRuntime.modelFallbackMessage);
        this.patchedSessions = new WeakSet();
        this.streaming = false;
        this.compacting = false;
        this.bashRunning = false;
        this.steeringMessages = [];
        this.followUpMessages = [];
        this.engineCallbackActive = false;
        this.diagnosticListeners = new Set();
        this.remoteModels = [];
        this.remoteScopedModels = [];
        this.pendingDiagnostics = [];
        this.autoCompactionEnabled = true;
        this.autoRetryEnabled = true;
        this.client = client;
        this.remoteCommands = new RemoteCommandCatalog(client);
        this.client.onEvent((event) => this.observeEvent(event));
    }
    get session() {
        const session = super.session;
        this.patchSession(session);
        return session;
    }
    async initializeFromEngine() {
        const state = await this.client.getState();
        const session = super.session;
        const catalog = await this.client.requestInternal({ type: "get_available_models" });
        this.remoteModels = catalog.models;
        this.remoteScopedModels = catalog.scopedModels ?? [];
        this.patchModelCatalog(session);
        if (state.model)
            session.agent.state.model = state.model;
        session.agent.state.thinkingLevel = state.thinkingLevel;
        session.agent.steeringMode = state.steeringMode;
        session.agent.followUpMode = state.followUpMode;
        this.autoCompactionEnabled = state.autoCompactionEnabled;
        this.remoteSessionName = state.sessionName;
        this.remoteSessionFile = state.sessionFile;
        this.streaming = state.isStreaming;
        this.compacting = state.isCompacting;
        if (state.sessionFile && session.sessionFile !== state.sessionFile)
            await super.switchSession(state.sessionFile);
        this.refreshSessionView();
        this.engineCallbackActive = false;
        // Non-blocking refresh so isolated autocomplete lists engine-only extension
        // commands after bind/restart/reload/new/resume/fork. See RemoteCommandCatalog.
        this.remoteCommands.refresh();
    }
    onDiagnostic(listener) {
        for (const diagnostic of this.pendingDiagnostics.splice(0))
            listener(diagnostic);
        this.diagnosticListeners.add(listener);
        return () => this.diagnosticListeners.delete(listener);
    }
    onEngineMessage(listener) {
        return this.client.onInteractiveEngineMessage(listener);
    }
    onKeybindingState(listener) {
        return this.client.onInteractiveEngineKeybindingState(listener);
    }
    sendEngineCommand(command) {
        this.client.sendInteractiveEngineCommand(command);
    }
    getRemoteCommands() { return this.remoteCommands.getCommands(); }
    onRemoteCommandsChanged(listener) { return this.remoteCommands.onChange(listener); }
    async invokeRemoteShortcut(key) {
        await this.client.requestInternal({ type: "invoke_shortcut", key });
    }
    waitUntilBound() { return this.client.waitForInteractiveEngineBound(); }
    getEnginePid() { return this.client.getEnginePid(); }
    getEngineGeneration() { return this.client.getGeneration(); }
    isRecovering() { return this.restartPromise !== undefined; }
    async synchronize() {
        const state = await this.client.getState();
        this.remoteSessionFile = state.sessionFile;
        this.remoteSessionName = state.sessionName;
    }
    setEngineCallbackActive(active) { this.engineCallbackActive = active; }
    interruptBlockedCallback() {
        if (!this.engineCallbackActive)
            return false;
        this.dispatchBestEffort("interrupt", this.session.abort());
        return true;
    }
    setExtensionUIHandler(handler) {
        return this.client.onExtensionUIRequest((request) => {
            void handler(request).then(async (response) => {
                if (response)
                    await this.client.respondExtensionUI(response);
            }).catch((error) => {
                this.emitDiagnostic({
                    activity: undefined,
                    elapsedMs: 0,
                    level: "unresponsive",
                    message: `Interactive engine UI bridge failed: ${error.message}`,
                });
            });
        });
    }
    emitDiagnostic(diagnostic) {
        this.lastDiagnostic = diagnostic;
        this.engineCallbackActive = true;
        if (this.diagnosticListeners.size === 0)
            this.pendingDiagnostics.push(diagnostic);
        for (const listener of this.diagnosticListeners)
            listener(diagnostic);
    }
    async switchSession(sessionPath) {
        const result = await this.client.switchSession(sessionPath);
        if (!result.cancelled) {
            await super.switchSession(sessionPath);
            await this.initializeFromEngine();
        }
        return result;
    }
    async newSession(options) {
        const result = await this.client.newSession(options?.parentSession);
        if (result.cancelled)
            return result;
        const state = await this.client.getState();
        if (state.sessionFile)
            await super.switchSession(state.sessionFile);
        else
            this.resetUnpersistedSessionView();
        await this.initializeFromEngine();
        return result;
    }
    async importFromJsonl(inputPath, cwdOverride) {
        const result = await this.client.requestInternal({
            type: "import_session", inputPath, cwdOverride,
        });
        if (result.cancelled)
            return result;
        const state = await this.client.getState();
        if (state.sessionFile)
            await super.switchSession(state.sessionFile);
        await this.initializeFromEngine();
        return result;
    }
    async fork(entryId, options) {
        let selectedText;
        let cancelled;
        if (options?.position === "at") {
            cancelled = (await this.client.clone()).cancelled;
        }
        else {
            const result = await this.client.fork(entryId);
            cancelled = result.cancelled;
            selectedText = result.text;
        }
        if (cancelled)
            return { cancelled: true };
        const state = await this.client.getState();
        if (state.sessionFile)
            await super.switchSession(state.sessionFile);
        await this.initializeFromEngine();
        return { cancelled: false, selectedText };
    }
    async dispose() {
        await this.client.stop();
        await super.dispose();
    }
    patchSession(session) {
        if (this.patchedSessions.has(session))
            return;
        this.patchedSessions.add(session);
        this.patchSessionManager(session.sessionManager);
        Object.defineProperties(session, {
            isStreaming: { configurable: true, get: () => this.streaming },
            isCompacting: { configurable: true, get: () => this.compacting },
            isBashRunning: { configurable: true, get: () => this.bashRunning },
            sessionName: { configurable: true, get: () => this.remoteSessionName },
            sessionFile: { configurable: true, get: () => this.remoteSessionFile },
            autoCompactionEnabled: { configurable: true, get: () => this.autoCompactionEnabled },
            autoRetryEnabled: { configurable: true, get: () => this.autoRetryEnabled },
            subscribe: {
                configurable: true,
                value: (listener) => this.client.onEvent(listener),
            },
            prompt: {
                configurable: true,
                value: async (text, options) => {
                    await this.client.prompt(text, options?.images, options?.streamingBehavior);
                    options?.preflightResult?.(true);
                },
            },
            steer: { configurable: true, value: (text) => this.client.steer(text) },
            followUp: { configurable: true, value: (text) => this.client.followUp(text) },
            abort: { configurable: true, value: () => this.abortAndRecover() },
            executeBash: {
                configurable: true,
                value: async (command, onChunk, options) => {
                    this.bashRunning = true;
                    try {
                        const result = await this.client.requestInternal({
                            type: "user_bash", command, excludeFromContext: options?.excludeFromContext,
                        });
                        if (result.output)
                            onChunk?.(result.output);
                        return result;
                    }
                    finally {
                        this.bashRunning = false;
                    }
                },
            },
            recordBashResult: { configurable: true, value: () => { } },
            abortBash: { configurable: true, value: () => this.dispatchBestEffort("abort bash", this.client.abortBash()) },
            compact: { configurable: true, value: () => this.client.compact() },
            abortCompaction: { configurable: true, value: () => this.dispatchBestEffort("abort compaction", this.client.requestInternal({ type: "abort_compaction" })) },
            abortRetry: { configurable: true, value: () => this.dispatchBestEffort("abort retry", this.client.abortRetry()) },
            navigateTree: {
                configurable: true,
                value: async (targetId, options) => this.client.requestInternal({
                    type: "navigate_tree", targetId, options,
                }),
            },
            reload: {
                configurable: true,
                value: async () => {
                    await this.client.requestInternal({ type: "reload" });
                    await this.initializeFromEngine();
                },
            },
            setSessionName: {
                configurable: true,
                value: (name) => {
                    this.remoteSessionName = name;
                    this.dispatchBestEffort("set session name", this.client.setSessionName(name).then(() => this.refreshSessionView()));
                },
            },
            getSteeringMessages: { configurable: true, value: () => [...this.steeringMessages] },
            getFollowUpMessages: { configurable: true, value: () => [...this.followUpMessages] },
            clearQueue: {
                configurable: true,
                value: () => {
                    const queued = { steering: [...this.steeringMessages], followUp: [...this.followUpMessages] };
                    this.steeringMessages = [];
                    this.followUpMessages = [];
                    this.dispatchBestEffort("clear queue", this.client.requestInternal({ type: "clear_queue" }));
                    return queued;
                },
            },
            setModel: {
                configurable: true,
                value: async (model) => {
                    const selected = await this.client.setModel(model.provider, model.id);
                    session.agent.state.model = session.modelRegistry.find(selected.provider, selected.id) ?? model;
                },
            },
            setThinkingLevel: {
                configurable: true,
                value: (level) => {
                    session.agent.state.thinkingLevel = level;
                    this.dispatchBestEffort("set thinking level", this.client.setThinkingLevel(level));
                },
            },
            cycleModel: {
                configurable: true,
                value: async (direction) => {
                    const result = await this.client.cycleModel(direction);
                    if (!result)
                        return undefined;
                    const model = session.modelRegistry.find(result.model.provider, result.model.id) ?? result.model;
                    session.agent.state.model = model;
                    session.agent.state.thinkingLevel = result.thinkingLevel;
                    return { ...result, model };
                },
            },
            cycleThinkingLevel: {
                configurable: true,
                value: () => {
                    const levels = session.getAvailableThinkingLevels();
                    if (levels.length <= 1)
                        return undefined;
                    const current = levels.indexOf(session.thinkingLevel);
                    const level = levels[(current + 1) % levels.length];
                    session.agent.state.thinkingLevel = level;
                    this.dispatchBestEffort("cycle thinking level", this.client.setThinkingLevel(level));
                    return level;
                },
            },
            setContextWindow: {
                configurable: true,
                value: (tokens) => {
                    if (session.agent.state.model)
                        session.agent.state.model = { ...session.agent.state.model, contextWindow: tokens };
                    this.dispatchBestEffort("set context window", this.client.setContextWindow(tokens));
                },
            },
            setSteeringMode: {
                configurable: true,
                value: (mode) => {
                    session.agent.steeringMode = mode;
                    this.dispatchBestEffort("set steering mode", this.client.setSteeringMode(mode));
                },
            },
            setFollowUpMode: {
                configurable: true,
                value: (mode) => {
                    session.agent.followUpMode = mode;
                    this.dispatchBestEffort("set follow-up mode", this.client.setFollowUpMode(mode));
                },
            },
            setAutoCompactionEnabled: {
                configurable: true,
                value: (enabled) => {
                    this.autoCompactionEnabled = enabled;
                    this.dispatchBestEffort("set auto compaction", this.client.setAutoCompaction(enabled));
                },
            },
            setAutoRetryEnabled: {
                configurable: true,
                value: (enabled) => {
                    this.autoRetryEnabled = enabled;
                    this.dispatchBestEffort("set auto retry", this.client.setAutoRetry(enabled));
                },
            },
        });
    }
    async abortAndRecover() {
        if (this.restartPromise)
            return this.restartPromise;
        const cooperativeAbort = this.client.abort().then(() => true, () => false);
        if (await Promise.race([cooperativeAbort, sleep(250).then(() => false)])) {
            this.engineCallbackActive = false;
            return;
        }
        const activity = this.lastDiagnostic?.activity;
        const label = activity ? `${activity.kind} ${activity.name}` : "engine callback";
        const diagnostic = {
            activity,
            elapsedMs: this.lastDiagnostic?.elapsedMs ?? 0,
            level: "unresponsive",
            message: `Engine terminated; ${label} result unknown; inspect side effects before retrying`,
        };
        this.streaming = false;
        this.compacting = false;
        this.engineCallbackActive = false;
        for (const listener of this.diagnosticListeners)
            listener(diagnostic);
        this.restartPromise = (async () => {
            await this.client.restart(this.remoteSessionFile);
            await this.initializeFromEngine();
        })().catch((error) => {
            this.emitDiagnostic({ ...diagnostic, message: `${diagnostic.message}; engine restart failed: ${error.message}` });
        }).finally(() => { this.restartPromise = undefined; });
        await this.restartPromise;
    }
    dispatchBestEffort(label, operation) {
        void operation.catch((error) => {
            if (this.restartPromise)
                return;
            this.emitDiagnostic({
                activity: undefined,
                elapsedMs: 0,
                level: "unresponsive",
                message: `Interactive engine ${label} failed: ${error.message}`,
            });
        });
    }
    resetUnpersistedSessionView() {
        const session = super.session;
        const manager = SessionManager.create(session.sessionManager.getCwd(), session.sessionManager.getSessionDir());
        Object.defineProperty(session, "sessionManager", { configurable: true, value: manager });
        this.patchSessionManager(manager);
        session.agent.state.messages = [];
    }
    patchSessionManager(manager) {
        Object.defineProperty(manager, "appendLabelChange", {
            configurable: true,
            value: (entryId, label) => {
                this.dispatchBestEffort("set label", this.client.requestInternal({ type: "set_label", entryId, label }).then(() => this.refreshSessionView()));
            },
        });
    }
    refreshSessionView() {
        const session = super.session;
        const sessionFile = session.sessionFile;
        if (!sessionFile)
            return;
        const currentManager = session.sessionManager;
        const refreshed = SessionManager.open(sessionFile, currentManager.getSessionDir(), currentManager.getCwd());
        Object.defineProperty(session, "sessionManager", { configurable: true, value: refreshed });
        this.patchSessionManager(refreshed);
        session.agent.state.messages = refreshed.buildSessionContext().messages;
    }
    patchModelCatalog(session) {
        const registry = session.modelRegistry;
        Object.defineProperties(registry, {
            getAvailable: { configurable: true, value: () => [...this.remoteModels] },
            find: {
                configurable: true,
                value: (provider, modelId) => this.remoteModels.find((model) => model.provider === provider && model.id === modelId),
            },
            hasConfiguredAuth: {
                configurable: true,
                value: (model) => this.remoteModels.some((candidate) => candidate.provider === model.provider && candidate.id === model.id),
            },
        });
        Object.defineProperty(session, "scopedModels", {
            configurable: true,
            get: () => this.remoteScopedModels,
        });
    }
    observeEvent(event) {
        const session = super.session;
        switch (event.type) {
            case "agent_start":
                this.streaming = true;
                break;
            case "agent_end":
                this.streaming = false;
                this.refreshSessionView();
                break;
            case "compaction_start":
                this.compacting = true;
                break;
            case "compaction_end":
                this.compacting = false;
                break;
            case "queue_update":
                this.steeringMessages = [...event.steering];
                this.followUpMessages = [...event.followUp];
                break;
            case "model_changed":
                session.agent.state.model = event.model;
                break;
            case "thinking_level_changed":
                session.agent.state.thinkingLevel = event.level;
                break;
            case "context_window_changed":
                if (session.agent.state.model)
                    session.agent.state.model = { ...session.agent.state.model, contextWindow: event.contextWindow };
                break;
            case "session_info_changed":
                this.remoteSessionName = event.name;
                break;
        }
    }
}
//# sourceMappingURL=isolated-runtime.js.map