import { basename } from "node:path";
import { extensionForImageMimeType, readClipboardImage } from "@sheason/pi-coding-agent";
import { CombinedAutocompleteProvider, Container, Loader, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, } from "@sheason/pi-tui";
import { getErrorMessage } from "../../../hub/utils.js";
import { parsePeerCommand } from "../../commands/index.js";
import { createPeerExtensionRunner } from "../../extensions/peer-extension-runner.js";
import { createPeerExtensionUIContext } from "../../extensions/peer-extension-ui-context.js";
import { AssistantMessageComponent, CustomEditor, getEditorTheme, getMarkdownTheme, initTheme, KeybindingsManager, ToolExecutionComponent, theme, UserMessageComponent, } from "../components/index.js";
import { RemoteAgentSelectorComponent } from "./components/agent-selector.js";
import { DPiRowComponent } from "./components/d-pi-row.js";
import { ForkedFooterComponent } from "./components/footer.js";
import { buildForkedStartupHelp, keyText } from "./components/keybinding-hints.js";
import { RemoteMcpDetailSelectorComponent } from "./components/mcp-detail-selector.js";
import { RemoteMcpListSelectorComponent } from "./components/mcp-list-selector.js";
import { RemoteModelSelectorComponent } from "./components/model-selector.js";
import { RemoteSettingsSelectorComponent } from "./components/settings-selector.js";
import { RemoteSkillDetailSelectorComponent } from "./components/skill-detail-selector.js";
import { RemoteSkillListSelectorComponent } from "./components/skill-list-selector.js";
import { RemoteSourceDetailSelectorComponent } from "./components/source-detail-selector.js";
import { RemoteSourceListSelectorComponent } from "./components/source-list-selector.js";
const CONNECTION_RETRY_ACTION = "app.connection.retry";
function isExpandable(obj) {
    return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
}
class ExpandableText extends Text {
    getCollapsedText;
    getExpandedText;
    constructor(getCollapsedText, getExpandedText, expanded = false, paddingX = 0, paddingY = 0) {
        super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);
        this.getCollapsedText = getCollapsedText;
        this.getExpandedText = getExpandedText;
    }
    setExpanded(expanded) {
        this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
    }
}
function serverNameForResourceId(servers, resourceId) {
    return servers.find((server) => server.resourceId === resourceId)?.name;
}
export class ForkedInteractiveMode {
    deps;
    ui;
    root = new Container();
    headerContainer = new Container();
    chatContainer = new Container();
    pendingMessagesContainer = new Container();
    statusContainer = new Container();
    editorContainer = new Container();
    footer;
    dPiRow;
    keybindings;
    editor;
    markdownTheme = getMarkdownTheme();
    statusLoader;
    statusLoaderMessage;
    workingStatusTimer;
    workingStatusTimerStartedAt;
    scheduledRender;
    activeSelector;
    activeSelectorKind;
    unsubscribers = [];
    signalCleanupHandlers = [];
    shutdownResolver;
    isShuttingDown = false;
    lastClearActionTime = 0;
    currentSourceStatuses = [];
    currentMcpServers = [];
    currentMcpConfigError;
    currentSkills = [];
    currentSkillDiagnostics = [];
    userMessageComponents = new Map();
    assistantMessageComponents = new Map();
    toolExecutionComponents = new Map();
    pendingMessageComponents = new Map();
    lastRenderedSignatures;
    liveAssistantComponent;
    extensionRunner;
    builtInHeader;
    toolOutputExpanded = false;
    hideThinkingBlock = false;
    pendingImages = [];
    constructor(deps, options = {}) {
        this.deps = deps;
        initTheme(options.themeName);
        this.ui = new TUI(new ProcessTerminal(), false);
        this.keybindings = KeybindingsManager.create();
        this.installPeerKeybindings();
        setKeybindings(this.keybindings);
        this.editor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
            paddingX: 1,
            autocompleteMaxVisible: 8,
        });
        this.editor.setAutocompleteProvider(this.createAutocompleteProvider());
        this.footer = new ForkedFooterComponent();
        this.dPiRow = new DPiRowComponent();
        this.installEditorHandlers();
        this.buildLayout();
        void this.initExtensionRunner();
    }
    async initExtensionRunner() {
        const runner = await createPeerExtensionRunner({
            cwd: this.deps.cwd,
            getView: () => this.deps.getView(),
            actions: this.deps.actions,
            capabilities: this.deps.capabilities,
            peerId: this.deps.peerId,
        });
        const uiContext = createPeerExtensionUIContext({
            ui: this.ui,
            editor: this.editor,
            keybindings: this.keybindings,
            editorContainer: this.editorContainer,
            statusContainer: this.statusContainer,
            requestRender: () => this.ui.requestRender(),
            setTerminalTitle: (title) => this.ui.terminal.setTitle(title),
        });
        runner.setUIContext(uiContext);
        runner.bindCommandContext(createStubCommandContextActions());
        this.extensionRunner = runner;
    }
    installPeerKeybindings() {
        const currentBindings = this.keybindings.getUserBindings();
        const newlineKeys = this.keybindings.getKeys("tui.input.newLine");
        const retryKeys = this.keybindings.getKeys("app.connection.retry");
        const hasConfiguredRetryKeys = Object.hasOwn(currentBindings, "app.connection.retry");
        if (newlineKeys.includes("ctrl+j") && (retryKeys.length > 0 || hasConfiguredRetryKeys)) {
            return;
        }
        this.keybindings.setUserBindings({
            ...currentBindings,
            ...(newlineKeys.includes("ctrl+j") ? {} : { "tui.input.newLine": [...newlineKeys, "ctrl+j"] }),
            ...(retryKeys.length > 0 || hasConfiguredRetryKeys ? {} : { "app.connection.retry": ["ctrl+r"] }),
        });
    }
    async run() {
        try {
            this.registerSignalHandlers();
            this.ui.start();
            this.subscribeToState();
            this.ui.terminal.setTitle(`D-Pi Peer - ${basename(this.deps.cwd || process.cwd())}`);
            this.renderFromState();
            this.ui.requestRender();
            return await new Promise((resolve) => {
                this.shutdownResolver = resolve;
            });
        }
        catch (error) {
            this.ui.stop();
            throw error;
        }
    }
    async stop() {
        this.extensionRunner?.invalidate("Peer session shutdown");
        this.extensionRunner = undefined;
        this.statusLoader?.stop();
        this.statusLoader = undefined;
        this.statusLoaderMessage = undefined;
        this.stopWorkingStatusTimer();
        this.cancelScheduledRender();
        for (const unsubscribe of this.unsubscribers) {
            unsubscribe();
        }
        this.unsubscribers = [];
        this.unregisterSignalHandlers();
        await new Promise((resolve) => process.nextTick(resolve));
        await this.ui.terminal.drainInput(1000);
        this.ui.stop();
    }
    buildLayout() {
        this.restoreEditor();
        this.root.addChild(this.headerContainer);
        this.root.addChild(this.chatContainer);
        this.root.addChild(this.pendingMessagesContainer);
        this.root.addChild(this.statusContainer);
        this.root.addChild(this.editorContainer);
        this.root.addChild(this.dPiRow);
        this.root.addChild(this.footer);
        this.ui.addChild(this.root);
        this.ui.setFocus(this.editor);
        this.renderStatusArea(this.deps.getView());
    }
    installEditorHandlers() {
        this.editor.onSubmit = (text) => {
            void this.queueInput(text);
        };
        this.editor.onChange = (text) => {
            this.deps.setDraft(text);
        };
        this.editor.onEscape = () => {
            void this.handleEscape();
        };
        this.editor.onCtrlD = () => {
            void this.shutdown();
        };
        this.editor.onAction("app.clear", () => {
            void this.handleClearAction();
        });
        this.editor.onAction("app.message.followUp", () => {
            void this.queueInput(this.editor.getText(), "followUp");
        });
        this.editor.onAction("app.message.dequeue", () => {
            void this.handleDequeue();
        });
        this.editor.onAction(CONNECTION_RETRY_ACTION, () => {
            void this.deps.actions.retryConnection?.();
        });
        this.editor.onAction("app.model.select", () => {
            if (!this.deps.capabilities.supportsModelSelection) {
                this.appendWarningMessage('"/model" is disabled in this remote mode.');
                return;
            }
            this.openModelSelector();
        });
        this.editor.onAction("app.tools.expand", () => {
            this.toggleToolOutputExpansion();
        });
        this.editor.onAction("app.thinking.cycle", () => {
            this.cycleThinkingLevel();
        });
        this.editor.onAction("app.model.cycleForward", () => {
            void this.cycleModel("forward");
        });
        this.editor.onAction("app.model.cycleBackward", () => {
            void this.cycleModel("backward");
        });
        this.editor.onAction("app.thinking.toggle", () => {
            this.toggleThinkingBlockVisibility();
        });
        this.editor.onPasteImage = () => {
            void this.handleClipboardImagePaste();
        };
    }
    async handleEscape() {
        const session = this.deps.getView().session;
        if ((session?.queuedMessages?.length ?? 0) > 0) {
            await this.requireQueueFlush()();
            return;
        }
        const hasRemoteSession = this.deps.getView().session !== undefined && this.deps.getView().connection.state === "connected";
        if (hasRemoteSession) {
            const wasRunning = await this.deps.actions.abort();
            if (wasRunning) {
                return;
            }
        }
        if (this.editor.getText().length > 0) {
            this.deps.setDraft("");
            this.editor.setText("");
            this.ui.requestRender();
        }
    }
    subscribeToState() {
        this.unsubscribers.push(this.deps.subscribe(() => {
            const view = this.deps.getView();
            const dirtyFlags = this.computeRenderDirtyFlags(this.lastRenderedSignatures, view);
            if (this.canRenderDirtyStateDirectly(dirtyFlags)) {
                this.cancelScheduledRender();
                this.renderDirtyState(view, dirtyFlags);
                return;
            }
            if (this.shouldRenderLiveStateImmediately()) {
                if (this.tryRenderLiveStateIncrementally(view, dirtyFlags)) {
                    this.lastRenderedSignatures = getRenderViewSignatures(view);
                    return;
                }
                this.cancelScheduledRender();
                this.renderSubscribedState();
                return;
            }
            this.scheduleRenderFromState();
        }));
    }
    shouldRenderLiveStateImmediately() {
        const view = this.deps.getView();
        return view.session?.isRunning === true && view.live?.streamingMessage !== undefined;
    }
    tryRenderLiveStateIncrementally(view = this.deps.getView(), dirtyFlags) {
        if (dirtyFlags &&
            (dirtyFlags.layout || dirtyFlags.chat || dirtyFlags.queue || dirtyFlags.status || dirtyFlags.footer)) {
            return false;
        }
        const liveStreamingMessage = view.live?.streamingMessage;
        if (!view.session?.isRunning || !liveStreamingMessage) {
            return false;
        }
        if (liveStreamingMessage.content.some((content) => content.type === "toolCall")) {
            return false;
        }
        const snapshotMatch = findLiveStreamingAssistantSnapshot(view);
        if (snapshotMatch) {
            const componentKey = getSnapshotAssistantComponentKey(snapshotMatch.message, snapshotMatch.index);
            const cached = this.assistantMessageComponents.get(componentKey);
            if (!cached) {
                return false;
            }
            const signature = getAssistantMessageSignature(snapshotMatch.message);
            if (cached.signature !== signature) {
                cached.signature = signature;
                updateAssistantComponentContent(cached.component, snapshotMatch.message);
                this.ui.requestRender();
            }
            return true;
        }
        const componentKey = getLiveAssistantComponentKey(liveStreamingMessage, view.live?.streamingMessageId);
        if (!this.liveAssistantComponent || this.liveAssistantComponent.key !== componentKey) {
            return false;
        }
        this.updateLiveAssistantComponent(componentKey, liveStreamingMessage);
        this.ui.requestRender();
        return true;
    }
    computeRenderDirtyFlags(previousSignatures, nextView) {
        if (!previousSignatures) {
            return {
                layout: true,
                chat: true,
                liveText: true,
                queue: true,
                status: true,
                footer: true,
            };
        }
        const nextSignatures = getRenderViewSignatures(nextView);
        return {
            layout: previousSignatures.layout !== nextSignatures.layout,
            chat: previousSignatures.chat !== nextSignatures.chat,
            liveText: previousSignatures.liveText !== nextSignatures.liveText,
            queue: previousSignatures.queue !== nextSignatures.queue,
            status: previousSignatures.status !== nextSignatures.status,
            footer: previousSignatures.footer !== nextSignatures.footer,
        };
    }
    canRenderDirtyStateDirectly(dirtyFlags) {
        if (dirtyFlags.layout || dirtyFlags.chat) {
            return false;
        }
        return dirtyFlags.liveText || dirtyFlags.queue || dirtyFlags.status || dirtyFlags.footer;
    }
    renderDirtyState(view, dirtyFlags) {
        const draft = this.deps.getDraft();
        if (!this.activeSelector && this.editor.getText() !== draft) {
            this.editor.setText(draft);
        }
        this.updateActiveSelectorFromView(view);
        if (dirtyFlags.liveText && !this.tryRenderLiveStateIncrementally(view)) {
            this.renderMessages(view);
        }
        if (dirtyFlags.queue) {
            this.renderQueueArea(view);
        }
        if (dirtyFlags.status) {
            this.renderStatusArea(view);
        }
        if (dirtyFlags.footer || dirtyFlags.queue || dirtyFlags.status) {
            this.dPiRow.setView(view);
            this.footer.setView(view);
        }
        this.lastRenderedSignatures = getRenderViewSignatures(view);
        this.ui.requestRender();
    }
    scheduleRenderFromState() {
        if (this.scheduledRender) {
            return;
        }
        this.scheduledRender = setImmediate(() => {
            this.scheduledRender = undefined;
            if (this.isShuttingDown) {
                return;
            }
            this.renderSubscribedState();
        });
        this.scheduledRender.unref?.();
    }
    renderSubscribedState() {
        const draft = this.deps.getDraft();
        if (!this.activeSelector && this.editor.getText() !== draft) {
            this.editor.setText(draft);
        }
        this.renderFromState();
    }
    cancelScheduledRender() {
        if (!this.scheduledRender) {
            return;
        }
        clearImmediate(this.scheduledRender);
        this.scheduledRender = undefined;
    }
    renderFromState() {
        const view = this.deps.getView();
        this.updateActiveSelectorFromView(view);
        this.renderHeader(view);
        this.renderMessages(view);
        this.renderPendingMessages(view);
        this.renderStatusArea(view);
        this.dPiRow.setView(view);
        this.footer.setView(view);
        this.lastRenderedSignatures = getRenderViewSignatures(view);
        this.ui.requestRender();
    }
    updateActiveSelectorFromView(view) {
        if (this.activeSelectorKind !== "agent-list" || !(this.activeSelector instanceof RemoteAgentSelectorComponent)) {
            return;
        }
        this.activeSelector.updateAgents(view.agents ?? [], view.footer.boundAgentId);
    }
    renderHeader(view) {
        this.headerContainer.clear();
        const logo = theme.bold(theme.fg("accent", "D-Pi Peer")) + theme.fg("dim", " remote");
        const startupHelp = buildForkedStartupHelp(this.deps.capabilities);
        const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
        const onboarding = theme.fg("dim", "Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.");
        this.builtInHeader = new ExpandableText(() => `${logo}\n${startupHelp.compact}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${startupHelp.expanded}\n\n${onboarding}`, this.toolOutputExpanded, 1, 0);
        this.headerContainer.addChild(this.builtInHeader);
        const statusLines = [
            view.status.connectionMessage,
            ...view.status.diagnostics,
            view.status.lastError ?? getLatestToolErrorText(view.session),
        ].filter((value) => Boolean(value));
        if (statusLines.length > 0) {
            this.headerContainer.addChild(new Spacer(1));
            for (const line of statusLines) {
                this.headerContainer.addChild(new Text(theme.fg("warning", line), 1, 0));
            }
        }
        this.headerContainer.addChild(new Spacer(1));
    }
    renderMessages(view) {
        this.chatContainer.clear();
        const snapshot = view.session;
        if (!snapshot) {
            this.chatContainer.addChild(new Text(theme.fg("dim", "Connecting to hub..."), 1, 0));
            return;
        }
        const items = snapshot.items ?? [];
        const toolResults = new Map();
        for (const item of items) {
            if (item.type === "message" && item.message.role === "toolResult") {
                toolResults.set(item.message.toolCallId, item.message);
            }
        }
        const pendingToolCallIds = new Set(snapshot.pendingToolCallIds);
        const liveToolExecutions = new Map((view.live?.toolExecutions ?? []).map((execution) => [execution.toolCallId, execution]));
        const renderedToolCallIds = new Set();
        const activeUserComponentKeys = new Set();
        const activeAssistantComponentKeys = new Set();
        const activeToolCallIds = new Set();
        let hasRenderedUserMessage = false;
        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            if (item.type === "run_timing") {
                this.renderMetricsLines([formatRunTimingLine(item.timing.durationMs, item.timing.endReason)]);
                continue;
            }
            const message = item.message;
            switch (message.role) {
                case "user": {
                    const text = getUserMessageText(message);
                    if (!text.trim()) {
                        break;
                    }
                    if (hasRenderedUserMessage) {
                        this.chatContainer.addChild(new Spacer(1));
                    }
                    const componentKey = getSnapshotUserComponentKey(message, i);
                    activeUserComponentKeys.add(componentKey);
                    this.renderUserMessage(componentKey, text, message.messageSource);
                    hasRenderedUserMessage = true;
                    break;
                }
                case "assistant": {
                    const componentKey = getSnapshotAssistantComponentKey(message, i);
                    activeAssistantComponentKeys.add(componentKey);
                    this.renderAssistantMessage(message, toolResults, pendingToolCallIds, liveToolExecutions, renderedToolCallIds, { snapshotComponentKey: componentKey });
                    break;
                }
                case "toolResult":
                    break;
                default:
                    this.chatContainer.addChild(new Spacer(1));
                    this.chatContainer.addChild(new Text(theme.fg("dim", JSON.stringify(message)), 1, 0));
            }
        }
        const liveStreamingMessage = view.live?.streamingMessage;
        const shouldRenderLiveStreamingMessage = !!liveStreamingMessage &&
            !items.some((item) => item.type === "message" &&
                item.message.role === "assistant" &&
                isSameLiveStreamingAssistantMessage(item.message, liveStreamingMessage, view.live?.streamingMessageId));
        if (shouldRenderLiveStreamingMessage && liveStreamingMessage) {
            this.renderAssistantMessage(liveStreamingMessage, toolResults, pendingToolCallIds, liveToolExecutions, renderedToolCallIds, { liveComponentKey: getLiveAssistantComponentKey(liveStreamingMessage, view.live?.streamingMessageId) });
            this.renderMetricsLines([formatLiveRunTimingLine(snapshot)]);
        }
        else {
            this.liveAssistantComponent = undefined;
        }
        for (const toolCallId of renderedToolCallIds) {
            activeToolCallIds.add(toolCallId);
        }
        this.renderLiveToolExecutions(liveToolExecutions, renderedToolCallIds, snapshot.isRunning);
        for (const execution of liveToolExecutions.values()) {
            if (snapshot.isRunning || execution.result === undefined) {
                activeToolCallIds.add(execution.toolCallId);
            }
        }
        if (items.length === 0 && !shouldRenderLiveStreamingMessage) {
            this.chatContainer.addChild(new Text(theme.fg("dim", "Session is ready. Enter a prompt to begin."), 1, 0));
        }
        this.pruneCachedMessageComponents(activeUserComponentKeys, activeAssistantComponentKeys, activeToolCallIds);
    }
    renderMetricsLines(timingLines) {
        const renderableTimingLines = timingLines.filter((line) => Boolean(line));
        if (renderableTimingLines.length === 0) {
            return;
        }
        this.chatContainer.addChild(new Spacer(1));
        for (const line of renderableTimingLines) {
            this.chatContainer.addChild(new Text(theme.fg("dim", line), 1, 0));
        }
    }
    renderAssistantMessage(message, toolResults, pendingToolCallIds, liveToolExecutions, renderedToolCallIds, options = {}) {
        const component = options.liveComponentKey
            ? this.updateLiveAssistantComponent(options.liveComponentKey, message)
            : options.snapshotComponentKey
                ? this.updateSnapshotAssistantComponent(options.snapshotComponentKey, message)
                : new AssistantMessageComponent(message, this.hideThinkingBlock, this.markdownTheme);
        this.chatContainer.addChild(component);
        for (const content of message.content) {
            if (content.type !== "toolCall") {
                continue;
            }
            renderedToolCallIds.add(content.id);
            const liveExecution = liveToolExecutions.get(content.id);
            const toolResult = toolResults.get(content.id);
            const signature = getSnapshotToolExecutionSignature(content, liveExecution, toolResult, pendingToolCallIds.has(content.id));
            const updateComponent = (component) => {
                component.updateArgs(content.arguments);
                component.setArgsComplete();
                if (pendingToolCallIds.has(content.id) || toolResult || liveExecution) {
                    component.markExecutionStarted();
                }
                if (liveExecution?.partialResult) {
                    component.updateResult({ ...liveExecution.partialResult, isError: false }, true);
                }
                if (toolResult) {
                    component.updateResult(toolResult, false);
                }
                else if (liveExecution?.result) {
                    component.updateResult({ ...liveExecution.result, isError: liveExecution.isError ?? false }, false);
                }
            };
            const component = this.updateToolExecutionComponent(content.id, signature, () => new ToolExecutionComponent(content.name, content.id, content.arguments, {}, undefined, this.ui, this.deps.cwd), updateComponent);
            this.chatContainer.addChild(component);
        }
    }
    renderUserMessage(componentKey, text, messageSource) {
        const messageSourceJson = JSON.stringify(messageSource ?? null);
        let cached = this.userMessageComponents.get(componentKey);
        if (!cached || cached.text !== text || cached.messageSourceJson !== messageSourceJson) {
            cached = {
                text,
                messageSourceJson,
                component: new UserMessageComponent(text, this.markdownTheme, messageSource),
            };
            this.userMessageComponents.set(componentKey, cached);
        }
        this.chatContainer.addChild(cached.component);
    }
    updateSnapshotAssistantComponent(key, message) {
        const signature = getAssistantMessageSignature(message);
        let cached = this.assistantMessageComponents.get(key);
        if (!cached) {
            const component = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.markdownTheme);
            updateAssistantComponentContent(component, message);
            cached = { signature, component };
            this.assistantMessageComponents.set(key, cached);
            return component;
        }
        if (cached.signature !== signature) {
            cached.signature = signature;
            updateAssistantComponentContent(cached.component, message);
        }
        return cached.component;
    }
    updateLiveAssistantComponent(key, message) {
        if (!this.liveAssistantComponent || this.liveAssistantComponent.key !== key) {
            this.liveAssistantComponent = {
                key,
                component: new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.markdownTheme),
            };
        }
        updateAssistantComponentContent(this.liveAssistantComponent.component, message);
        return this.liveAssistantComponent.component;
    }
    updateToolExecutionComponent(toolCallId, signature, createComponent, updateComponent) {
        let cached = this.toolExecutionComponents.get(toolCallId);
        if (!cached) {
            const component = createComponent();
            updateComponent(component);
            cached = { signature, component };
            this.toolExecutionComponents.set(toolCallId, cached);
            return component;
        }
        if (cached.signature !== signature) {
            cached.signature = signature;
            updateComponent(cached.component);
        }
        return cached.component;
    }
    pruneCachedMessageComponents(activeUserKeys, activeAssistantKeys, activeToolCallIds) {
        for (const key of this.userMessageComponents.keys()) {
            if (!activeUserKeys.has(key)) {
                this.userMessageComponents.delete(key);
            }
        }
        for (const key of this.assistantMessageComponents.keys()) {
            if (!activeAssistantKeys.has(key)) {
                this.assistantMessageComponents.delete(key);
            }
        }
        for (const key of this.toolExecutionComponents.keys()) {
            if (!activeToolCallIds.has(key)) {
                this.toolExecutionComponents.delete(key);
            }
        }
    }
    renderLiveToolExecutions(liveToolExecutions, renderedToolCallIds, isRunning) {
        for (const execution of liveToolExecutions.values()) {
            if (renderedToolCallIds.has(execution.toolCallId)) {
                continue;
            }
            if (!isRunning && execution.result !== undefined) {
                continue;
            }
            const signature = getLiveToolExecutionSignature(execution);
            const updateComponent = (component) => {
                component.updateArgs(execution.args ?? {});
                component.setArgsComplete();
                component.markExecutionStarted();
                if (execution.partialResult) {
                    component.updateResult({ ...execution.partialResult, isError: false }, true);
                }
                if (execution.result) {
                    component.updateResult({ ...execution.result, isError: execution.isError ?? false }, false);
                }
            };
            const component = this.updateToolExecutionComponent(execution.toolCallId, signature, () => new ToolExecutionComponent(execution.toolName, execution.toolCallId, execution.args ?? {}, {}, undefined, this.ui, this.deps.cwd), updateComponent);
            this.chatContainer.addChild(component);
        }
    }
    async queueInput(text, streamingBehavior) {
        const trimmed = text.trim();
        if (!trimmed) {
            return;
        }
        const parsedCommand = parsePeerCommand(trimmed);
        this.deps.setDraft("");
        this.editor.setText("");
        const images = this.pendingImages.length > 0 ? [...this.pendingImages] : undefined;
        this.pendingImages = [];
        try {
            if (parsedCommand) {
                await this.handleParsedCommand(parsedCommand);
                return;
            }
            await this.requireQueueWrite()(trimmed, images, streamingBehavior);
            this.editor.addToHistory(trimmed);
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
        }
    }
    requireQueueWrite() {
        const queueWrite = this.deps.actions.queueWrite;
        if (queueWrite) {
            return queueWrite;
        }
        const submitPrompt = this.deps.actions.submitPrompt;
        if (!submitPrompt) {
            throw new Error("Queue write action is not available.");
        }
        return (text) => submitPrompt(text);
    }
    requireQueueFlush() {
        const queueFlush = this.deps.actions.queueFlush;
        if (!queueFlush) {
            throw new Error("Queue flush action is not available.");
        }
        return queueFlush;
    }
    async shutdown() {
        if (this.isShuttingDown) {
            return;
        }
        this.isShuttingDown = true;
        await this.stop();
        this.shutdownResolver?.(0);
    }
    registerSignalHandlers() {
        this.unregisterSignalHandlers();
        const signals = ["SIGINT", "SIGTERM"];
        if (process.platform !== "win32") {
            signals.push("SIGHUP");
        }
        for (const signal of signals) {
            const handler = () => {
                void this.shutdown();
            };
            process.on(signal, handler);
            this.signalCleanupHandlers.push(() => process.off(signal, handler));
        }
    }
    unregisterSignalHandlers() {
        for (const cleanup of this.signalCleanupHandlers) {
            cleanup();
        }
        this.signalCleanupHandlers = [];
    }
    async handleParsedCommand(command) {
        switch (command.kind) {
            case "set_model": {
                if (!this.deps.capabilities.supportsModelSelection) {
                    this.appendWarningMessage('"/model" is disabled in this remote mode.');
                    return;
                }
                const model = this.findModelResource(command.provider, command.modelId);
                if (!model) {
                    this.appendWarningMessage(`Unknown model: ${command.provider}/${command.modelId}`);
                    return;
                }
                if (!model.resourceId) {
                    this.appendWarningMessage(`Model ${command.provider}/${command.modelId} is missing resourceId.`);
                    return;
                }
                await this.deps.actions.setModel(model.resourceId);
                this.appendInfoMessage(`Active model changed to ${command.provider}/${command.modelId}.`);
                return;
            }
            case "show_model":
                if (!this.deps.capabilities.supportsModelSelection) {
                    this.appendWarningMessage('"/model" is disabled in this remote mode.');
                    return;
                }
                this.openModelSelector();
                return;
            case "show_agents":
                if (this.deps.capabilities.supportsAgentSwitching === false) {
                    this.appendWarningMessage('"/agents" is disabled in this remote mode.');
                    return;
                }
                this.openAgentSelector();
                return;
            case "set_thinking_level":
                if (this.deps.capabilities.supportsSettings === false) {
                    this.appendWarningMessage('"/settings" is disabled in this remote mode.');
                    return;
                }
                await this.deps.actions.setThinkingLevel(command.level);
                this.appendInfoMessage(`Thinking level changed to ${command.level}.`);
                return;
            case "show_settings":
                if (this.deps.capabilities.supportsSettings === false) {
                    this.appendWarningMessage('"/settings" is disabled in this remote mode.');
                    return;
                }
                this.openSettingsSelector();
                return;
            case "compact":
                if (!this.deps.capabilities.supportsCompact) {
                    this.appendWarningMessage('"/compact" is disabled in this remote mode.');
                    return;
                }
                await this.deps.actions.invokeCommand("compact", command.customInstructions);
                this.appendInfoMessage("Requested hub compaction.");
                return;
            case "reload":
                if (!this.deps.capabilities.supportsReload) {
                    this.appendWarningMessage('"/reload" is disabled in this remote mode.');
                    return;
                }
                await this.deps.actions.invokeCommand("reload");
                this.appendInfoMessage("Requested hub resource reload.");
                return;
            case "show_group":
                if (this.deps.capabilities.supportsGroup === false) {
                    this.appendWarningMessage('"/group" is disabled in this remote mode.');
                    return;
                }
                this.showGroupSummary();
                return;
            case "show_session":
                if (this.deps.capabilities.supportsSessionDetails === false) {
                    this.appendWarningMessage('"/session" is disabled in this remote mode.');
                    return;
                }
                this.showSessionSummary();
                return;
            case "show_sources": {
                if (this.deps.capabilities.supportsSources === false) {
                    this.appendWarningMessage('"/source" is disabled in this remote mode.');
                    return;
                }
                await this.openSourceSelector();
                return;
            }
            case "show_mcp_servers": {
                if (this.deps.capabilities.supportsMcp === false) {
                    this.appendWarningMessage('"/mcp" is disabled in this remote mode.');
                    return;
                }
                await this.openMcpSelector();
                return;
            }
            case "show_skills": {
                if (this.deps.capabilities.supportsSkills === false) {
                    this.appendWarningMessage('"/skills" is disabled in this remote mode.');
                    return;
                }
                await this.openSkillSelector();
                return;
            }
            case "disabled":
                this.appendCommandWarning(command.commandName, command.message);
                return;
            case "invalid":
                this.appendCommandWarning(command.commandName, command.message);
                return;
        }
    }
    showSessionSummary() {
        const snapshot = this.deps.getView().session;
        if (!snapshot) {
            this.appendWarningMessage("Session snapshot is not ready yet.");
            return;
        }
        let toolCalls = 0;
        const messages = getSessionMessages(snapshot);
        for (const message of messages) {
            if (message.role === "assistant") {
                toolCalls += message.content.filter((content) => content.type === "toolCall").length;
            }
        }
        const userMessages = messages.filter((message) => message.role === "user").length;
        const assistantMessages = messages.filter((message) => message.role === "assistant").length;
        const toolResults = messages.filter((message) => message.role === "toolResult").length;
        this.appendInfoMessage([
            "Session Info",
            `Session file: ${snapshot.sessionFile}`,
            `Messages: ${messages.length}`,
            `User: ${userMessages}`,
            `Assistant: ${assistantMessages}`,
            `Tool calls: ${toolCalls}`,
            `Tool results: ${toolResults}`,
            `Pending tools: ${snapshot.pendingToolCallIds.length}`,
            `Queued inputs: ${snapshot.queuedMessages?.length ?? 0}`,
        ].join("\n"));
    }
    showGroupSummary() {
        const app = this.deps.getView();
        const agents = app.agents ?? [];
        if (agents.length === 0) {
            this.appendWarningMessage("Group snapshot is not ready yet.");
            return;
        }
        const selfId = app.welcome?.agentId ?? "root";
        const agentLines = agents.map((agent) => {
            const suffix = agent.id === selfId ? " (you)" : "";
            const status = agent.isRunning ? "working" : "idle";
            return `- ${agent.id}${suffix}: ${status}, messages=${agent.messageCount}`;
        });
        const peerLines = app.peers.map((peer) => {
            const label = peer.displayName ? `${peer.peerId} (${peer.displayName})` : peer.peerId;
            return `- ${label}: agent=${peer.agentId}, tools=${peer.tools.length}, cwd=${peer.cwd ?? "unknown"}`;
        });
        this.appendInfoMessage([
            "Group Info",
            `Self: ${selfId}`,
            "Agents:",
            ...(agentLines.length > 0 ? agentLines : ["- none"]),
            "Executors:",
            '- host: D-Pi hub host workspace, use peer-id "host"',
            ...(peerLines.length > 0 ? peerLines : ["- no connected peers"]),
            "Tips:",
            "- Use message_agent for targeted collaboration.",
            "- Use broadcast_message_to_agents for shared updates.",
        ].join("\n"));
    }
    toggleToolOutputExpansion() {
        this.toolOutputExpanded = !this.toolOutputExpanded;
        this.setToolsExpanded(this.toolOutputExpanded);
    }
    setToolsExpanded(expanded) {
        this.toolOutputExpanded = expanded;
        if (this.builtInHeader && isExpandable(this.builtInHeader)) {
            this.builtInHeader.setExpanded(expanded);
        }
        for (const child of this.chatContainer.children) {
            if (isExpandable(child)) {
                child.setExpanded(expanded);
            }
        }
        this.ui.requestRender();
    }
    cycleThinkingLevel() {
        const session = this.deps.getView().session;
        if (!session || session.availableThinkingLevels.length === 0) {
            this.appendInfoMessage("Current model does not support thinking");
            return;
        }
        const levels = session.availableThinkingLevels;
        const currentIndex = levels.indexOf(session.thinkingLevel);
        const nextIndex = (currentIndex + 1) % levels.length;
        const nextLevel = levels[nextIndex];
        void this.deps.actions.setThinkingLevel(nextLevel);
        this.appendInfoMessage(`Thinking level: ${nextLevel}`);
    }
    async cycleModel(direction) {
        const session = this.deps.getView().session;
        if (!session || session.availableModels.length === 0) {
            this.appendInfoMessage("No models available");
            return;
        }
        if (!this.deps.capabilities.supportsModelSelection) {
            this.appendWarningMessage("Model cycling is disabled in this remote mode.");
            return;
        }
        const models = session.availableModels;
        const current = session.model;
        const currentIndex = current
            ? models.findIndex((m) => m.provider === current.provider && m.modelId === current.modelId)
            : -1;
        const step = direction === "forward" ? 1 : -1;
        const nextIndex = currentIndex === -1 ? 0 : (currentIndex + step + models.length) % models.length;
        const nextModel = models[nextIndex];
        if (!nextModel.resourceId) {
            this.appendWarningMessage(`Model ${nextModel.provider}/${nextModel.modelId} is missing resourceId.`);
            return;
        }
        try {
            await this.deps.actions.setModel(nextModel.resourceId);
            this.appendInfoMessage(`Switched to ${nextModel.provider}/${nextModel.modelId}`);
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
        }
    }
    toggleThinkingBlockVisibility() {
        this.hideThinkingBlock = !this.hideThinkingBlock;
        for (const cached of this.assistantMessageComponents.values()) {
            cached.component.setHideThinkingBlock(this.hideThinkingBlock);
        }
        if (this.liveAssistantComponent) {
            this.liveAssistantComponent.component.setHideThinkingBlock(this.hideThinkingBlock);
        }
        this.ui.requestRender();
        this.appendInfoMessage(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
    }
    async handleClipboardImagePaste() {
        try {
            const image = await readClipboardImage();
            if (!image) {
                return;
            }
            const data = Buffer.from(image.bytes).toString("base64");
            this.pendingImages.push({ data, mimeType: image.mimeType });
            const ext = extensionForImageMimeType(image.mimeType) ?? "png";
            this.editor.insertTextAtCursor?.(`[pasted-image.${ext}]`);
            this.ui.requestRender();
        }
        catch {
            // Silently ignore clipboard errors
        }
    }
    appendInfoMessage(text) {
        this.chatContainer.addChild(new Spacer(1));
        this.chatContainer.addChild(new Text(theme.fg("dim", text), 1, 0));
        this.ui.requestRender();
    }
    appendWarningMessage(text) {
        this.chatContainer.addChild(new Spacer(1));
        this.chatContainer.addChild(new Text(theme.fg("warning", text), 1, 0));
        this.ui.requestRender();
    }
    appendCommandWarning(commandName, message) {
        this.chatContainer.addChild(new Spacer(1));
        if (commandName.trim().length > 0) {
            this.chatContainer.addChild(new Text(theme.fg("dim", `/${commandName}`), 1, 0));
        }
        this.chatContainer.addChild(new Text(theme.fg("warning", message), 1, 0));
        this.ui.requestRender();
    }
    renderPendingMessages(view) {
        this.pendingMessagesContainer.clear();
        const steeringMessages = view.session?.steeringMessages ?? [];
        const followUpMessages = view.session?.followUpMessages ?? [];
        const queuedMessages = getQueuedMessages(view.session);
        const allPending = [
            ...steeringMessages.map((m) => ({ ...m, _displayMode: "steering" })),
            ...followUpMessages.map((m) => ({ ...m, _displayMode: "followUp" })),
            ...queuedMessages.map((m) => ({ ...m, _displayMode: "queued" })),
        ];
        if (allPending.length === 0) {
            this.pendingMessageComponents.clear();
            return;
        }
        this.pendingMessagesContainer.addChild(new Spacer(1));
        const activeQueueKeys = new Set();
        for (const message of allPending) {
            const source = `${message.messageSource.kind}/${message.messageSource.name}`;
            const signature = getQueuedMessageSignature(message);
            activeQueueKeys.add(signature);
            let component = this.pendingMessageComponents.get(signature);
            if (!component) {
                const label = message._displayMode === "steering" ? "Steering" : "Follow-up";
                component = new TruncatedText(theme.fg("dim", `${label} [${source}]: ${message.text}`), 1, 0);
                this.pendingMessageComponents.set(signature, component);
            }
            this.pendingMessagesContainer.addChild(component);
        }
        for (const key of this.pendingMessageComponents.keys()) {
            if (!activeQueueKeys.has(key)) {
                this.pendingMessageComponents.delete(key);
            }
        }
        const hintText = theme.fg("dim", `↳ ${keyText("app.message.dequeue")} to edit all queued messages`);
        this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
    }
    renderQueueArea(view) {
        this.renderPendingMessages(view);
    }
    restoreEditor() {
        this.activeSelector = undefined;
        this.activeSelectorKind = undefined;
        this.editorContainer.clear();
        this.editorContainer.addChild(this.editor);
        this.ui.setFocus(this.editor);
    }
    showSelector(kind, create) {
        const done = () => {
            this.closeSelector();
        };
        const { component, focus } = create(done);
        this.activeSelector = component;
        this.activeSelectorKind = kind;
        this.editorContainer.clear();
        this.editorContainer.addChild(component);
        this.ui.setFocus(focus);
        this.ui.requestRender();
    }
    closeSelector() {
        this.restoreEditor();
        this.ui.requestRender();
    }
    openModelSelector() {
        const session = this.deps.getView().session;
        if (!session || session.availableModels.length === 0) {
            this.appendWarningMessage("No selectable models were provided by D-Pi hub.");
            return;
        }
        this.showSelector("model", (done) => {
            const selector = new RemoteModelSelectorComponent(session.availableModels, session.model, async (model) => {
                if (!model.resourceId) {
                    this.appendWarningMessage(`Model ${model.provider}/${model.modelId} is missing resourceId.`);
                    return;
                }
                await this.deps.actions.setModel(model.resourceId);
                done();
            }, () => done(), (error) => {
                this.appendWarningMessage(getErrorMessage(error));
            });
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    openAgentSelector() {
        const view = this.deps.getView();
        const agents = view.agents ?? [];
        if (agents.length === 0) {
            this.appendWarningMessage("No agents are visible to the current token yet.");
            return;
        }
        const currentAgentId = view.footer.boundAgentId;
        const constraint = this.resolveAgentSelectionConstraint(view);
        if (this.isEveryAgentBlocked(agents, constraint)) {
            const reason = constraint.blockedReason ?? "No switchable agents available for your client type.";
            this.appendWarningMessage(reason);
            return;
        }
        this.showSelector("agent-list", (done) => {
            const selector = new RemoteAgentSelectorComponent(agents, currentAgentId, async (agent) => {
                await this.switchAgent(agent);
                done();
            }, () => done(), (error) => {
                this.appendWarningMessage(getErrorMessage(error));
            }, constraint);
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    resolveAgentSelectionConstraint(view) {
        const clientKind = view.welcome?.clientKind;
        if (clientKind === "guest") {
            return {
                blockedKind: "root",
                blockedReason: "Guest clients can only bind to guest agent ids.",
            };
        }
        if (clientKind === undefined || clientKind === "peer" || clientKind === "host") {
            return {
                blockedKind: "guest",
                blockedReason: 'Guest agent ids require clientKind "guest".',
            };
        }
        return {};
    }
    isEveryAgentBlocked(agents, constraint) {
        if (!constraint.blockedKind) {
            return false;
        }
        return agents.length > 0 && agents.every((agent) => agent.kind === constraint.blockedKind);
    }
    async switchAgent(agent) {
        const currentAgentId = this.deps.getView().footer.boundAgentId;
        if (agent.id === currentAgentId) {
            this.appendInfoMessage(`Already connected to agent ${agent.id}.`);
            return;
        }
        if (!this.deps.actions.switchAgent) {
            throw new Error("Agent switching action is not available.");
        }
        await this.deps.actions.switchAgent(agent.id);
        const label = agent.name ? `${agent.id} (${agent.name})` : agent.id;
        this.appendInfoMessage(`Switched to agent ${label}.`);
    }
    findModelResource(provider, modelId) {
        return this.deps
            .getView()
            .session?.availableModels.find((model) => model.provider === provider && model.modelId === modelId);
    }
    openSettingsSelector() {
        const session = this.deps.getView().session;
        if (!session || session.availableThinkingLevels.length === 0) {
            this.appendWarningMessage("No selectable settings were provided by D-Pi hub.");
            return;
        }
        this.showSelector("settings", (done) => {
            const selector = new RemoteSettingsSelectorComponent(session.thinkingLevel, session.availableThinkingLevels, async (level) => {
                await this.deps.actions.setThinkingLevel(level);
                done();
            }, () => done());
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    async openSourceSelector() {
        try {
            this.currentSourceStatuses = await this.deps.actions.getSessionSources();
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        if (this.currentSourceStatuses.length === 0) {
            this.appendInfoMessage("No sources are configured in .pi/sources.json.");
            return;
        }
        this.openSourceListSelector();
    }
    openSourceListSelector() {
        this.showSelector("source-list", () => {
            const selector = new RemoteSourceListSelectorComponent(this.currentSourceStatuses, (source) => {
                if (!source.resourceId) {
                    this.appendWarningMessage(`Source '${source.name}' is missing resourceId.`);
                    return;
                }
                this.openSourceDetailSelector(source.resourceId);
            }, () => this.closeSelector());
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    openSourceDetailSelector(resourceId) {
        const source = this.currentSourceStatuses.find((entry) => entry.resourceId === resourceId);
        if (!source) {
            this.openSourceListSelector();
            return;
        }
        this.showSelector("source-detail", () => {
            const sourceResourceId = source.resourceId ?? resourceId;
            const selector = new RemoteSourceDetailSelectorComponent(source, (action) => {
                void this.handleSourceDetailAction(sourceResourceId, action);
            }, () => this.openSourceListSelector());
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    async openMcpSelector() {
        try {
            const { servers, configError } = await this.deps.actions.getMcpServers();
            this.currentMcpServers = servers;
            this.currentMcpConfigError = configError;
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        this.openMcpListSelector();
    }
    async openSkillSelector() {
        if (!this.deps.actions.getSkills) {
            this.appendWarningMessage('"/skills" is not available in this remote mode.');
            return;
        }
        try {
            const { skills, diagnostics } = await this.deps.actions.getSkills();
            this.currentSkills = skills;
            this.currentSkillDiagnostics = diagnostics;
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        this.openSkillListSelector();
    }
    openSkillListSelector() {
        this.showSelector("skill-list", () => {
            const selector = new RemoteSkillListSelectorComponent(this.currentSkills, this.currentSkillDiagnostics, (skill) => this.openSkillDetailSelector(skill), () => this.closeSelector());
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    openSkillDetailSelector(skill) {
        this.showSelector("skill-detail", () => {
            const selector = new RemoteSkillDetailSelectorComponent(skill, () => this.openSkillListSelector());
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    openMcpListSelector() {
        this.showSelector("mcp-list", () => {
            const selector = new RemoteMcpListSelectorComponent(this.currentMcpServers, this.currentMcpConfigError, (server) => {
                if (!server.resourceId) {
                    this.appendWarningMessage(`MCP server '${server.name}' is missing resourceId.`);
                    return;
                }
                void this.openMcpDetailSelector(server.resourceId);
            }, () => {
                this.closeSelector();
            });
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    async openMcpDetailSelector(resourceId) {
        let status = this.currentMcpServers.find((entry) => entry.resourceId === resourceId);
        if (!status) {
            try {
                const { servers, configError } = await this.deps.actions.getMcpServers();
                this.currentMcpServers = servers;
                this.currentMcpConfigError = configError;
                status = this.currentMcpServers.find((entry) => entry.resourceId === resourceId);
            }
            catch (error) {
                this.appendWarningMessage(getErrorMessage(error));
                this.openMcpListSelector();
                return;
            }
        }
        if (!status) {
            this.openMcpListSelector();
            return;
        }
        const server = status;
        this.showSelector("mcp-detail", () => {
            const serverResourceId = server.resourceId ?? resourceId;
            const selector = new RemoteMcpDetailSelectorComponent(server, (action) => {
                void this.handleMcpDetailAction(serverResourceId, action);
            }, () => {
                this.openMcpListSelector();
            });
            return { component: selector, focus: selector.getFocusTarget() };
        });
    }
    async handleMcpDetailAction(resourceId, action) {
        try {
            switch (action) {
                case "pause":
                    await this.deps.actions.pauseMcpServer(resourceId);
                    break;
                case "restart":
                    await this.deps.actions.restartMcpServer(resourceId);
                    break;
                case "remove":
                    await this.deps.actions.removeMcpServer(resourceId);
                    break;
            }
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        try {
            const { servers, configError } = await this.deps.actions.getMcpServers();
            this.currentMcpServers = servers;
            this.currentMcpConfigError = configError;
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        const stillPresent = this.currentMcpServers.some((entry) => entry.resourceId === resourceId);
        const displayName = serverNameForResourceId(this.currentMcpServers, resourceId) ?? resourceId;
        if (!stillPresent) {
            this.appendInfoMessage(`MCP server '${displayName}' removed.`);
            this.openMcpListSelector();
            return;
        }
        if (action === "remove") {
            this.appendWarningMessage(`MCP server '${displayName}' could not be removed.`);
            this.openMcpListSelector();
            return;
        }
        if (action === "pause") {
            this.appendInfoMessage(`MCP server '${displayName}' paused.`);
        }
        else {
            this.appendInfoMessage(`MCP server '${displayName}' restarted.`);
        }
        await this.openMcpDetailSelector(resourceId);
    }
    async handleSourceDetailAction(resourceId, action) {
        try {
            switch (action) {
                case "pause":
                    this.currentSourceStatuses = await this.deps.actions.pauseSource(resourceId);
                    break;
                case "restart":
                    this.currentSourceStatuses = await this.deps.actions.restartSource(resourceId);
                    break;
                case "remove":
                    this.currentSourceStatuses = await this.deps.actions.removeSource(resourceId);
                    break;
            }
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
            return;
        }
        if (this.currentSourceStatuses.length === 0) {
            this.closeSelector();
            this.appendInfoMessage("No sources remain in .pi/sources.json.");
            return;
        }
        this.openSourceListSelector();
    }
    async handleClearAction() {
        const now = Date.now();
        if (now - this.lastClearActionTime < 500) {
            await this.shutdown();
            return;
        }
        this.lastClearActionTime = now;
        this.deps.setDraft("");
        this.editor.setText("");
        this.ui.requestRender();
    }
    async handleDequeue() {
        const queuedMessages = getQueuedMessages(this.deps.getView().session);
        if (queuedMessages.length === 0) {
            this.appendInfoMessage("No queued messages to restore.");
            return;
        }
        const queuedText = queuedMessages.map((message) => message.text).join("\n\n");
        const currentText = this.editor.getText();
        const combinedText = [queuedText, currentText].filter((text) => text.trim().length > 0).join("\n\n");
        try {
            await this.deps.actions.invokeCommand("dequeue");
            this.deps.setDraft(combinedText);
            this.editor.setText(combinedText);
            this.ui.requestRender();
        }
        catch (error) {
            this.appendWarningMessage(getErrorMessage(error));
        }
    }
    renderStatusArea(view) {
        if (view.session?.isRunning) {
            this.startWorkingStatusTimer(view.session.runStartedAt);
            const statusMessage = this.createWorkingStatusMessage(view);
            if (!this.statusLoader) {
                this.statusContainer.clear();
                this.statusLoaderMessage = statusMessage;
                this.statusLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), statusMessage);
                this.statusContainer.addChild(this.statusLoader);
            }
            else {
                this.setStatusLoaderMessage(statusMessage);
            }
            return;
        }
        this.stopWorkingStatusTimer();
        this.statusLoader?.stop();
        this.statusLoader = undefined;
        this.statusLoaderMessage = undefined;
        this.statusContainer.clear();
        const idleStatusMessage = view.status.liveStatusMessage ?? view.live?.statusMessage;
        if (idleStatusMessage) {
            this.statusContainer.addChild(new Spacer(1));
            this.statusContainer.addChild(new Text(theme.fg("dim", idleStatusMessage), 1, 0));
            return;
        }
        this.statusContainer.addChild(new Spacer(1));
    }
    createWorkingStatusMessage(view) {
        const elapsed = getElapsedRunDurationMs(view.session?.runStartedAt);
        const message = elapsed === undefined
            ? `Working... (${keyText("app.interrupt")} to interrupt)`
            : `Working ${formatPeerRunDuration(elapsed)}... (${keyText("app.interrupt")} to interrupt)`;
        return view.status.crdtResyncMessage ? `${message} · ${view.status.crdtResyncMessage}` : message;
    }
    startWorkingStatusTimer(runStartedAt) {
        if (!runStartedAt) {
            this.stopWorkingStatusTimer();
            return;
        }
        if (this.workingStatusTimer && this.workingStatusTimerStartedAt === runStartedAt) {
            return;
        }
        this.stopWorkingStatusTimer();
        this.workingStatusTimerStartedAt = runStartedAt;
        this.workingStatusTimer = setInterval(() => this.updateWorkingStatusMessage(), 1000);
        this.workingStatusTimer.unref?.();
    }
    updateWorkingStatusMessage() {
        if (!this.statusLoader) {
            return;
        }
        const view = this.deps.getView();
        this.setStatusLoaderMessage(this.createWorkingStatusMessage(view));
        if (view.session?.isRunning === true && view.live?.streamingMessage) {
            this.renderMessages(view);
        }
    }
    setStatusLoaderMessage(message) {
        if (this.statusLoaderMessage === message) {
            return;
        }
        this.statusLoaderMessage = message;
        this.statusLoader?.setMessage(message);
    }
    stopWorkingStatusTimer() {
        if (this.workingStatusTimer) {
            clearInterval(this.workingStatusTimer);
        }
        this.workingStatusTimer = undefined;
        this.workingStatusTimerStartedAt = undefined;
    }
    createAutocompleteProvider() {
        return new CombinedAutocompleteProvider(this.createSlashCommands(), this.deps.cwd, null);
    }
    createSlashCommands() {
        const commands = this.deps.getView().commands.map((command) => ({
            name: command.name,
            description: command.description,
        }));
        for (const command of commands) {
            if (command.name === "model") {
                command.argumentHint = "<provider>/<model-id>";
                command.getArgumentCompletions = async (argumentPrefix) => {
                    const models = this.deps.getView().session?.availableModels ?? [];
                    const normalizedPrefix = argumentPrefix.trim().toLowerCase();
                    const items = models
                        .map((model) => ({
                        value: `${model.provider}/${model.modelId}`,
                        label: model.modelId,
                        description: model.provider,
                    }))
                        .filter((item) => normalizedPrefix.length === 0
                        ? true
                        : `${item.value} ${item.label} ${item.description}`.toLowerCase().includes(normalizedPrefix));
                    return items.length > 0 ? items : null;
                };
            }
            if (command.name === "settings") {
                command.argumentHint = "thinking <level>";
                command.getArgumentCompletions = async (argumentPrefix) => {
                    const normalizedPrefix = argumentPrefix.trim().toLowerCase();
                    const settingsPrefix = "thinking";
                    if (normalizedPrefix.length === 0 || settingsPrefix.startsWith(normalizedPrefix)) {
                        return [{ value: "thinking ", label: "thinking", description: "Change thinking level" }];
                    }
                    if (!normalizedPrefix.startsWith("thinking")) {
                        return null;
                    }
                    const levelPrefix = normalizedPrefix.replace(/^thinking\s*/, "");
                    const levels = this.deps.getView().session?.availableThinkingLevels ?? [];
                    const items = levels
                        .filter((level) => levelPrefix.length === 0 || level.toLowerCase().includes(levelPrefix))
                        .map((level) => ({
                        value: `thinking ${level}`,
                        label: level,
                        description: "Thinking level",
                    }));
                    return items.length > 0 ? items : null;
                };
            }
        }
        return commands;
    }
}
function getUserMessageText(message) {
    const text = typeof message.content === "string"
        ? message.content
        : message.content
            .filter((content) => content.type === "text")
            .map((content) => content.text)
            .join("\n");
    return message.messageSource ? stripPersistedMessageSourceHeader(text) : text;
}
function stripPersistedMessageSourceHeader(text) {
    const lines = text.split("\n");
    if (!lines[0]?.startsWith("[message source:")) {
        return text;
    }
    let index = 0;
    while (index < lines.length && isPersistedMessageSourceHeaderLine(lines[index])) {
        index += 1;
    }
    return lines.slice(index).join("\n");
}
function isPersistedMessageSourceHeaderLine(line) {
    return (line.startsWith("[message source:") ||
        line.startsWith("[message sent at:") ||
        line.startsWith("[security note:") ||
        line.startsWith("[message source auth token") ||
        line.startsWith("[message source user:") ||
        line.startsWith("[message source purpose:"));
}
function getElapsedRunDurationMs(runStartedAt) {
    if (!runStartedAt) {
        return undefined;
    }
    const startedAtMs = Date.parse(runStartedAt);
    if (!Number.isFinite(startedAtMs)) {
        return undefined;
    }
    return Math.max(0, Date.now() - startedAtMs);
}
function formatPeerRunDuration(durationMs) {
    const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = totalSeconds % 60;
    return `${String(minutes).padStart(2, "0")}m${String(seconds).padStart(2, "0")}s`;
}
function formatRunTimingLine(durationMs, reason) {
    const suffix = reason === "interrupted" ? " (interrupted)" : reason === "error" ? " (error)" : "";
    return `Turn duration: ${formatPeerRunDuration(durationMs)}${suffix}`;
}
function formatLiveRunTimingLine(snapshot) {
    const durationMs = getElapsedRunDurationMs(snapshot.runStartedAt);
    return durationMs === undefined ? undefined : formatRunTimingLine(durationMs, snapshot.lastRunEndReason);
}
function getSessionMessages(snapshot) {
    return (snapshot.items ?? []).flatMap((item) => (item.type === "message" ? [item.message] : []));
}
function getLatestToolErrorText(snapshot) {
    if (!snapshot) {
        return undefined;
    }
    const messages = getSessionMessages(snapshot);
    for (let i = messages.length - 1; i >= 0; i -= 1) {
        const message = messages[i];
        if (message.role !== "toolResult" || !message.isError) {
            continue;
        }
        const text = message.content
            .filter((content) => content.type === "text")
            .map((content) => content.text)
            .join("\n")
            .trim();
        if (text.length > 0) {
            return text;
        }
    }
    return undefined;
}
function getAssistantMessageId(message) {
    return `assistant:${message.timestamp}`;
}
function getRenderViewSignatures(view) {
    return {
        layout: getLayoutSignature(view),
        chat: getChatSignature(view),
        liveText: getLiveSignature(view),
        queue: getQueueSignature(view),
        status: getStatusSignature(view),
        footer: getFooterSignature(view),
    };
}
function getLayoutSignature(view) {
    return [
        view.welcome?.sessionId,
        view.welcome?.protocolVersion,
        view.welcome?.hubVersion,
        view.welcome?.agentId,
        view.connection.state,
        view.connection.message,
        view.peers.length,
        view.commands.map((command) => `${command.name}:${command.description}`).join("\u0001"),
        view.session?.header?.id,
        view.session?.header?.version,
        view.session?.availableModels
            .map((model) => `${model.resourceId}:${model.provider}/${model.modelId}`)
            .join("\u0001"),
        view.session?.availableThinkingLevels.join("\u0001"),
    ].join("\u0002");
}
function getChatSignature(view) {
    const liveMessage = view.live?.streamingMessage;
    const liveMessageId = view.live?.streamingMessageId;
    return (view.session?.items ?? [])
        .map((item, index) => {
        if (item.type === "run_timing") {
            return `run_timing:${index}:${item.timing.durationMs}:${item.timing.endReason}`;
        }
        const message = item.message;
        if (message.role === "user") {
            const messageSource = message.messageSource;
            const source = messageSource ? `${messageSource.kind}/${messageSource.name}` : "unknown";
            return `user:${index}:${String(message.timestamp)}:${source}:${getTextSignature(getUserMessageText(message))}`;
        }
        if (message.role === "assistant") {
            if (liveMessage && isSameLiveStreamingAssistantMessage(message, liveMessage, liveMessageId)) {
                return `assistant-live:${index}:${String(message.timestamp)}:${liveMessageId ?? ""}`;
            }
            return `assistant:${index}:${String(message.timestamp)}:${getAssistantMessageSignature(message)}`;
        }
        if (message.role === "toolResult") {
            return `toolResult:${index}:${message.toolCallId}:${message.isError === true}:${getToolResultSignature(message)}`;
        }
        return `${index}:${JSON.stringify(message)}`;
    })
        .join("\u0002");
}
function getLiveSignature(view) {
    const live = view.live;
    if (!live) {
        return "";
    }
    const streamingMessageSignature = live.streamingMessage
        ? getAssistantMessageSignature(live.streamingMessage)
        : "no-streaming-message";
    const toolSignature = live.toolExecutions.map(getLiveToolExecutionSignature).join("\u0001");
    return [
        live.streamingMessageId,
        live.streamingMessageIndex,
        streamingMessageSignature,
        toolSignature,
        live.statusMessage,
    ].join("\u0002");
}
function getQueueSignature(view) {
    const steering = view.session?.steeringMessages ?? [];
    const followUp = view.session?.followUpMessages ?? [];
    const queued = getQueuedMessages(view.session);
    return [
        ...steering.map((m) => `s:${getQueuedMessageSignature(m)}`),
        ...followUp.map((m) => `f:${getQueuedMessageSignature(m)}`),
        ...queued.map((m) => `q:${getQueuedMessageSignature(m)}`),
    ].join("\u0002");
}
function getQueuedMessageSignature(message) {
    return [message.messageSource.kind, message.messageSource.name, message.text.length, message.text].join("\u0001");
}
function getStatusSignature(view) {
    return [
        view.connection.state,
        view.status.connectionMessage,
        view.status.liveStatusMessage,
        view.status.crdtResyncMessage,
        view.status.lastError,
        view.status.diagnostics.join("\u0001"),
        view.session?.isRunning === true,
        view.session?.runStartedAt,
        view.live?.statusMessage,
    ].join("\u0002");
}
function getFooterSignature(view) {
    const footer = view.footer;
    return [
        footer.cwd,
        footer.modelLabel,
        footer.queueSummary,
        footer.pendingToolCount,
        footer.peerCount,
        footer.isRunning,
        footer.peerId,
        footer.boundAgentId,
        footer.sessionId,
        footer.contextWindow,
        footer.contextUsage?.percent,
    ].join("\u0002");
}
function findLiveStreamingAssistantSnapshot(view) {
    const liveStreamingMessage = view.live?.streamingMessage;
    if (!liveStreamingMessage || !view.session) {
        return undefined;
    }
    const items = view.session.items ?? [];
    const streamingIndex = view.live?.streamingMessageIndex;
    if (streamingIndex !== undefined) {
        const item = items[streamingIndex];
        if (item?.type === "message" && item.message.role === "assistant") {
            return { index: streamingIndex, message: item.message };
        }
    }
    for (let i = 0; i < items.length; i += 1) {
        const item = items[i];
        const message = item?.type === "message" ? item.message : undefined;
        if (message?.role === "assistant" &&
            isSameLiveStreamingAssistantMessage(message, liveStreamingMessage, view.live?.streamingMessageId)) {
            return { index: i, message };
        }
    }
    return undefined;
}
function getSnapshotUserComponentKey(message, index) {
    return `user:${index}:${String(message.timestamp)}`;
}
function getSnapshotAssistantComponentKey(message, index) {
    return `assistant:${index}:${message.timestamp}`;
}
function getLiveAssistantComponentKey(message, liveStreamingMessageId) {
    return liveStreamingMessageId ?? `live-assistant:${message.timestamp}`;
}
function getAssistantMessageSignature(message) {
    const contentSignature = message.content.map(getAssistantContentSignature).join("\u0001");
    return [contentSignature, message.stopReason, message.errorMessage].join("\u0002");
}
function getAssistantContentSignature(content) {
    if (content.type === "text") {
        return `text:${getTextSignature(content.text)}`;
    }
    if (content.type === "thinking") {
        return `thinking:${getTextSignature(content.thinking)}`;
    }
    if (content.type === "toolCall") {
        return `toolCall:${content.id}:${content.name}:${getStableValueSignature(content.arguments)}`;
    }
    return getStableValueSignature(content);
}
function getSnapshotToolExecutionSignature(content, liveExecution, toolResult, isPending) {
    return [
        content.id,
        content.name,
        isPending,
        getStableValueSignature(content.arguments),
        liveExecution ? getLiveToolExecutionSignature(liveExecution) : "",
        toolResult ? getToolResultSignature(toolResult) : "",
    ].join("\u0002");
}
function getLiveToolExecutionSignature(execution) {
    return [
        execution.toolCallId,
        execution.toolName,
        getStableValueSignature(execution.args ?? {}),
        getStableValueSignature(execution.partialResult ?? null),
        getStableValueSignature(execution.result ?? null),
        execution.isError === true,
    ].join("\u0002");
}
function getToolResultSignature(message) {
    return [
        message.toolCallId,
        message.isError === true,
        message.content
            .map((content) => content.type === "text" ? `text:${getTextSignature(content.text)}` : getStableValueSignature(content))
            .join("\u0001"),
    ].join("\u0002");
}
function getTextSignature(text) {
    return `${text.length}:${hashString(text)}`;
}
function hashString(text) {
    let hash = 2166136261;
    for (let i = 0; i < text.length; i += 1) {
        hash ^= text.charCodeAt(i);
        hash = Math.imul(hash, 16777619);
    }
    return (hash >>> 0).toString(36);
}
function getStableValueSignature(value) {
    return JSON.stringify(value);
}
export function updateAssistantComponentContent(component, message) {
    if (component.tryUpdateContentIncrementally?.(message)) {
        return;
    }
    component.updateContent(message);
}
function isSameLiveStreamingAssistantMessage(message, liveStreamingMessage, liveStreamingMessageId) {
    if (liveStreamingMessageId && getAssistantMessageId(message) === liveStreamingMessageId) {
        return true;
    }
    return message.timestamp === liveStreamingMessage.timestamp;
}
function getQueuedMessages(snapshot) {
    return [...(snapshot?.queuedMessages ?? [])];
}
const REMOTE_COMMAND_ERROR = "not available in remote mode";
function createStubCommandContextActions() {
    return {
        waitForIdle: async () => { },
        newSession: async () => {
            throw new Error(`newSession ${REMOTE_COMMAND_ERROR}`);
        },
        fork: async () => {
            throw new Error(`fork ${REMOTE_COMMAND_ERROR}`);
        },
        navigateTree: async () => {
            throw new Error(`navigateTree ${REMOTE_COMMAND_ERROR}`);
        },
        switchSession: async () => {
            throw new Error(`switchSession ${REMOTE_COMMAND_ERROR}`);
        },
        reload: async () => {
            throw new Error(`reload ${REMOTE_COMMAND_ERROR}`);
        },
    };
}
//# sourceMappingURL=interactive-mode.js.map