/**
 * AgentSession - Core abstraction for agent lifecycle and session management.
 *
 * This class is shared between all run modes (interactive, print, rpc).
 * It encapsulates:
 * - AgentController state access
 * - Event subscription with automatic session persistence
 * - Model and thinking level management
 * - Compaction (manual and auto)
 * - Bash execution
 * - Session switching and branching
 *
 * Modes use this class and add their own I/O layer on top.
 */
import { SessionState } from '../core/session-state.js';
import { createCompactionActions, createErrorRecoveryActions, triggerCompactionIfNeeded } from '../core/session/compaction-bridge.js';
import { isContextOverflow } from '../core/session/context-overflow.js';
import { SessionCompaction } from '../core/session/session-compaction.js';
import { classifyError, SessionErrorRecovery } from '../core/session/session-error-recovery.js';
import { getActiveUserMessageId } from '../core/user-message-id.js';
import { isShortcutFastModeModel } from '../model-ids.js';
import { formatFileUploadsContext, mergeFileUploads } from './file-uploads.js';
import { createShortcutLlmProxyClient } from './providers/shortcut-llm-proxy-client.js';
import { expandPromptTemplate } from './resources/prompt-template-expansion.js';
import { parseSkillBlock } from './resources/skill-block.js';
import { AUTONOMOUS_STATE_CUSTOM_TYPE, buildAutonomousRefreshPrompt, extractProgressFolder, inferAutonomousStateFromEntries } from './session/autonomous-workflow.js';
import { BranchManager } from './session/branch-manager.js';
import { parseBudgetCommand } from './session/budget-command.js';
import { emitExtensionEvent } from './session/extension-emitter.js';
import { ExtensionLifecycle } from './session/extension-lifecycle.js';
import { ModelManager } from './session/model-manager.js';
import { resolvePendingRefreshOverride } from './session/pending-refresh-overrides.js';
import { formatRuntimePermissionBypassReminder } from './session/permission-context.js';
import { PersistenceHandler } from './session/persistence-handler.js';
import { assertNotExtensionCommand, buildMessageContent, normalizeUserContent, tryExtensionCommand } from './session/prompt-pipeline.js';
import { QueueTracker } from './session/queue-tracker.js';
import { buildRunAgentMetadata, emitRunAgent, summarizeDeliveredUserContent } from './session/run-agent-observability.js';
import { SessionBudgetContinuation } from './session/session-budget-continuation.js';
import { executeNewSession, executeSwitchSession } from './session/session-lifecycle.js';
import { getAvailableThinkingLevels, supportsThinking, supportsXhighThinking } from './session/session-models.js';
import { computeContextUsage, computeSessionStats } from './session/session-stats.js';
import { expandSkillCommand } from './session/skill-expansion.js';
import { formatSystemReminderContext } from './session/system-reminder-context.js';
import { ToolRegistry } from './session/tool-registry.js';
import { ToolSummaryEmitter } from './session/tool-summary-emitter.js';
import { isToolSummaryEligible } from './session/tool-summary-policy.js';
// ============================================================================
// The interactive mode catches this to auto-trigger the Shortcut login flow.
// ============================================================================
export class LoginRequiredError extends Error {
    /** Construct a LoginRequiredError with an optional human-readable message. */
    constructor(message = 'You must log in to Shortcut first.') {
        super(message);
        this.name = 'LoginRequiredError';
    }
}
export { parseSkillBlock } from './resources/skill-block.js';
// ============================================================================
// AgentSession Class
// ============================================================================
export class AgentSession {
    agent;
    sessionManager;
    settingsManager;
    approvalState;
    // Event subscription state
    _unsubscribeAgent;
    _eventListeners = [];
    _agentEventQueue = Promise.resolve();
    // Event pipeline: queue tracker (owns steering/followUp message display state)
    _queueTracker = new QueueTracker();
    /** Messages queued to be included with the next user prompt as context ("asides"). */
    _pendingNextTurnMessages = [];
    // Compaction state machine (extracted module)
    _compaction;
    // Branch management (extracted module)
    _branches;
    // Error recovery state machine (extracted module)
    _recovery = new SessionErrorRecovery();
    // Budget continuation state machine (extracted module)
    _budgetContinuation = new SessionBudgetContinuation();
    // Budget limits passed from parent (subagent mode)
    _budgetLimits;
    // Pending context refresh (set by refresh_context tool, consumed by interactive mode)
    _pendingRefresh = null;
    // Autonomous workflow state (persists across refresh-created sessions)
    _autonomousState = null;
    // Event pipeline
    _turnIndex = 0; // shared counter — passed to both extension emitter and persistence
    _persistence = new PersistenceHandler();
    _pendingImmediateRunAgentMetadata;
    _resourceLoader;
    _prePromptContext;
    /** Details from the last prePromptContext call (e.g. { workbooks: string[] }). Read by TUI for annotations. */
    lastPrePromptDetails;
    _sessionState = new SessionState();
    /** Session-scoped file upload metadata. */
    _fileUploads = [];
    // Collaboration client (lazily created on first /collab use)
    _collabClient = null;
    // Sheet engine pool (set from main.ts when new-sheet mode is active)
    _sheetPool = null;
    // Collaboration awareness (created after joining a room with an active workbook)
    _collabAwareness = null;
    // CRDT sync orchestrator (created after joining a room)
    _collabSync = null;
    // Model registry for API key resolution
    _modelRegistry;
    /** Fire-and-forget tool-call summary generation. */
    _summaryEmitter;
    _toolSummaryDiagnostics = [];
    _observability;
    // Tool registry — owns tool maps, prompt snippets, system prompt rebuild
    _tools;
    // Model manager — model switching, cycling, thinking level
    _models;
    _fastModeAllowed = true;
    _fastModeDisabledReason;
    // Extension lifecycle — owns extension runner, bindings, reload
    _extensions;
    // Bridge objects for compaction/recovery/budget subsystems
    _compactionActions;
    _compactionTriggerDeps;
    _errorRecoveryActions;
    /** Wire up subsystems (tools, models, extensions, compaction, recovery) from a session config. */
    constructor(config) {
        this.agent = config.agent;
        this.sessionManager = config.sessionManager;
        this.settingsManager = config.settingsManager;
        this.approvalState = config.approvalState;
        this._compaction = new SessionCompaction({ complete: config.compactionComplete });
        this._resourceLoader = config.resourceLoader;
        this._modelRegistry = config.modelRegistry;
        this._prePromptContext = config.prePromptContext;
        this._budgetLimits = config.budgetLimits;
        this._observability = config.observability;
        this._fastModeAllowed = config.fastModeAllowed ?? true;
        this._fastModeDisabledReason = this._fastModeAllowed
            ? undefined
            : (config.fastModeDisabledReason ?? 'team');
        const summaryShortcutLlmProxy = createShortcutLlmProxyClient({
            modelRegistry: this._modelRegistry,
            getAttribution: () => ({
                sessionId: this.sessionManager.getSessionId(),
                userMessageId: getActiveUserMessageId(this.messages)
            })
        });
        this._summaryEmitter = new ToolSummaryEmitter({
            enabled: config.enableToolSummaries ?? true,
            isFeatureEnabled: () => this.settingsManager.getToolSummariesEnabled(),
            isEligibleTool: isToolSummaryEligible,
            getShortcutLlmProxyClient: () => summaryShortcutLlmProxy,
            onDiagnostic: (diagnostic) => {
                this._toolSummaryDiagnostics.push(diagnostic);
                if (this._toolSummaryDiagnostics.length > 200) {
                    this._toolSummaryDiagnostics.shift();
                }
            }
        });
        // Construct extracted modules with stable deps
        this._tools = new ToolRegistry(this.agent, this._resourceLoader, this.settingsManager, config.cwd, config.initialPermissionPolicy);
        this._models = new ModelManager(this.agent, this._modelRegistry, this.sessionManager, this.settingsManager);
        this._branches = new BranchManager({
            agent: this.agent,
            sessionManager: this.sessionManager,
            getSessionFile: () => this.sessionFile,
            clearPendingNextTurnMessages: () => {
                this._pendingNextTurnMessages = [];
            },
            observability: this._observability
        });
        this._extensions = new ExtensionLifecycle({
            resourceLoader: config.resourceLoader,
            cwd: config.cwd,
            extensionRunnerRef: config.extensionRunnerRef,
            runtimeTools: config.tools ?? [],
            reloadTools: config.reloadTools,
            sessionManager: this.sessionManager,
            modelRegistry: this._modelRegistry,
            sessionState: this._sessionState,
            settingsManager: this.settingsManager,
            tools: this._tools
        }, {
            getModel: () => this.model,
            isIdle: () => !this.isStreaming,
            abort: () => this.abort(),
            hasPendingMessages: () => this.pendingMessageCount > 0,
            shutdown: () => {
                /* handled by bindExtensions */
            },
            getContextUsage: () => this.getContextUsage(),
            compact: (options) => {
                void (async () => {
                    try {
                        const result = await this.compact(options?.customInstructions);
                        options?.onComplete?.(result);
                    }
                    catch (error) {
                        const err = error instanceof Error ? error : new Error(String(error));
                        options?.onError?.(err);
                    }
                })();
            },
            getSystemPrompt: () => this.systemPrompt,
            appendEntry: (customType, data) => {
                this.sessionManager.appendCustomEntry(customType, data);
            },
            deductChildUsage: (usage) => {
                this._budgetContinuation.deductExternalTokens(usage.input + usage.output);
                // Note: child turns do NOT count toward parent's turn limit.
                // The parent "used one tool" (the task tool) regardless of child turns.
            },
            getMessages: () => this.messages,
            getTools: () => this.agent.state.tools,
            getPermissionPolicy: () => this.permissionPolicy
        });
        // Build compaction action bridges
        this._compactionActions = createCompactionActions({
            extensionRunner: () => this._extensions.extensionRunner,
            sessionManager: this.sessionManager,
            agent: this.agent,
            removeLastErrorGroup: () => this._removeLastErrorGroup()
        });
        this._compactionTriggerDeps = {
            model: () => this.model,
            modelRegistry: this._modelRegistry,
            sessionManager: this.sessionManager,
            compaction: this._compaction,
            compactionActions: this._compactionActions,
            agent: this.agent
        };
        this._errorRecoveryActions = createErrorRecoveryActions({
            agent: this.agent,
            model: () => this.model,
            modelRegistry: this._modelRegistry,
            sessionManager: this.sessionManager,
            compaction: this._compaction,
            compactionActions: () => this._compactionActions,
            removeLastErrorGroup: () => this._removeLastErrorGroup(),
            setModel: (model) => this.setModel(model)
        });
        // Forward extracted module events to session listeners
        this._recovery.onEvent((event) => this._emit(event));
        this._compaction.onEvent((event) => this._emit(event));
        this._budgetContinuation.onEvent((event) => this._emit(event));
        // Always subscribe to agent events for internal handling
        // (session persistence, extensions, auto-compaction, retry logic)
        this._unsubscribeAgent = this.agent.subscribe(this._handleAgentLoopEvent);
        this._extensions.buildRuntime({
            toolNames: config.initialToolNames,
            modeName: config.initialModeName
        });
    }
    /** Model registry for API key resolution and model discovery */
    get modelRegistry() {
        return this._modelRegistry;
    }
    /** Return a snapshot of the most recent tool-summary emitter diagnostics. */
    getToolSummaryDiagnostics() {
        return [...this._toolSummaryDiagnostics];
    }
    /** Active runtime permission policy applied to tool calls in this session. */
    get permissionPolicy() {
        return this._tools.getPermissionPolicy();
    }
    // =========================================================================
    // Event Subscription
    // =========================================================================
    /** Emit an event to all listeners */
    _emit(event) {
        for (const l of this._eventListeners) {
            l(event);
        }
    }
    /**
     * Remove the last errored assistant message and its synthetic error
     * toolResult messages from agent state. When a streaming call fails
     * mid-response, agent-loop appends [assistant(error), toolResult, ...]
     * — this removes the entire group so retries start from a clean state.
     */
    _removeLastErrorGroup() {
        const messages = this.agent.state.messages;
        let i = messages.length - 1;
        while (i >= 0 && messages[i].role === 'toolResult')
            i--;
        if (i >= 0 &&
            messages[i].role === 'assistant' &&
            messages[i].stopReason === 'error') {
            this.agent.replaceMessages(messages.slice(0, i));
        }
    }
    /** Internal handler for agent events - shared by subscribe and reconnect */
    _handleAgentLoopEvent = (event) => {
        // Create retry promise synchronously before queueing async processing.
        // AgentController.emit() calls this handler synchronously, and prompt() calls waitForRetry()
        // as soon as agent.prompt() resolves. If _retryPromise is created only inside
        // _processAgentLoopEvent, slow earlier queued events can delay agent_end processing
        // and waitForRetry() can miss the in-flight retry.
        this._createRetryPromiseForAgentEnd(event);
        this._agentEventQueue = this._agentEventQueue.then(() => this._processAgentLoopEvent(event), () => this._processAgentLoopEvent(event));
        // Keep queue alive if an event handler fails
        this._agentEventQueue.catch(() => { });
    };
    /** True when the assistant message indicates the conversation has overflowed the model context. */
    _checkContextOverflow = (msg) => {
        return isContextOverflow(msg, this.model?.contextWindow ?? 0);
    };
    /** On agent_end, eagerly arm the recovery retry promise before queued event processing runs. */
    _createRetryPromiseForAgentEnd(event) {
        if (event.type !== 'agent_end')
            return;
        const lastAssistant = this._findLastAssistantInMessages(event.messages);
        this._recovery.prepareForAgentEnd(lastAssistant, this._checkContextOverflow, this.settingsManager.getErrorRecoverySettings());
    }
    /** Find the last assistant message in a transcript, scanning from the tail. */
    _findLastAssistantInMessages(messages) {
        for (let i = messages.length - 1; i >= 0; i--) {
            const message = messages[i];
            if (message.role === 'assistant') {
                return message;
            }
        }
        return undefined;
    }
    /** Attach a budget-related note to a tool result so the model sees the limit context. */
    _appendBudgetAnnotationToToolResult(message, annotation) {
        const budgetText = `[budget] ${annotation.text}`;
        const content = Array.isArray(message.content) ? [...message.content] : [];
        const lastTextBlock = content.length > 0 ? content[content.length - 1] : undefined;
        if (lastTextBlock?.type === 'text') {
            const existingText = lastTextBlock.text ?? '';
            if (!existingText.includes(budgetText)) {
                lastTextBlock.text =
                    existingText.length > 0 ? `${existingText}\n${budgetText}` : budgetText;
            }
        }
        else {
            content.push({ type: 'text', text: budgetText });
            message.content = content;
        }
        const existingDetails = message.details && typeof message.details === 'object'
            ? message.details
            : {};
        message.details = {
            ...existingDetails,
            budgetStatus: annotation.details
        };
    }
    /** Serialized handler for each agent-loop event: persistence, extensions, compaction, recovery. */
    async _processAgentLoopEvent(event) {
        // --- Step 1: Queue tracking (sync) ---
        this._queueTracker.handle(event);
        if (event.type === 'message_start' && event.message.role === 'user') {
            const deliveredContent = summarizeDeliveredUserContent(event.message.content);
            const pendingMetadata = this._pendingImmediateRunAgentMetadata;
            const conversationId = this.sessionManager.getSessionId();
            const userMessageId = typeof event.message.id === 'string' && event.message.id.length > 0
                ? event.message.id
                : undefined;
            this._pendingImmediateRunAgentMetadata = undefined;
            emitRunAgent(this._observability, {
                ...(pendingMetadata ??
                    buildRunAgentMetadata({
                        charCount: deliveredContent.charCount,
                        imageCount: deliveredContent.imageCount,
                        conversationId,
                        userMessageId
                    })),
                conversationId,
                thread_id: conversationId,
                platform: 'cli',
                char_count: deliveredContent.charCount,
                message_length: deliveredContent.charCount,
                image_count: deliveredContent.imageCount,
                ...(userMessageId ? { user_message_id: userMessageId, turnId: userMessageId } : {})
            });
        }
        // --- Step 2: Extensions (async — must complete before persistence) ---
        // Reset turnIndex on agent_start BEFORE the snapshot so extensions see 0.
        if (event.type === 'agent_start') {
            this._turnIndex = 0;
            this._summaryEmitter.reset();
        }
        const turnIndex = this._turnIndex;
        await emitExtensionEvent(this._extensions.extensionRunner, event, turnIndex);
        if (event.type === 'message_end' && event.message.role === 'toolResult') {
            const annotation = this._budgetContinuation.buildTurnAnnotation();
            if (annotation) {
                this._appendBudgetAnnotationToToolResult(event.message, annotation);
            }
        }
        // --- Step 3: External listeners (sync) ---
        this._emit(event);
        // --- Step 3b: Tool summary capture & dispatch (fire-and-forget) ---
        if (event.type === 'tool_execution_start') {
            this._summaryEmitter.captureArgs(event.toolCallId, event.args);
        }
        if (event.type === 'tool_execution_end') {
            this._summaryEmitter.maybeGenerate(event.toolCallId, event.toolName, event.result, (e) => this._emit(e));
        }
        // --- Step 3c: Budget token accumulation (sync) ---
        if (event.type === 'message_end' && event.message.role === 'assistant') {
            this._budgetContinuation.accumulateTokens(event.message.usage.totalTokens);
        }
        // --- Step 3d: Budget turn/tool-use accumulation (sync) ---
        if (event.type === 'turn_end') {
            this._budgetContinuation.accumulateTurn();
        }
        if (event.type === 'tool_execution_end') {
            this._budgetContinuation.accumulateToolUse();
        }
        // --- Step 3e: Hard limit enforcement (sync) ---
        if (event.type === 'message_end' ||
            event.type === 'turn_end' ||
            event.type === 'tool_execution_end') {
            // Note: checkHardLimits is idempotent (returns true immediately if already reached).
            // Multiple accumulation events in the same turn (e.g., message_end + turn_end)
            // may each trigger this, but the deferred abort is harmless when called multiple times.
            const limits = this._getExecutionBudgetLimits();
            if (this._budgetContinuation.checkHardLimits(limits)) {
                // Defer abort to avoid reentrancy — abort() may emit events that
                // re-enter this pipeline before the current event finishes processing.
                queueMicrotask(() => this.agent.abort());
            }
        }
        // --- Step 4: Persistence (sync — writes timing + messages to SessionManager) ---
        if (event.type === 'agent_start') {
            this._persistence.startAgent(this.sessionManager, this.systemPrompt, this.agent.state.tools.map((t) => ({
                name: t.name,
                description: t.description,
                parameters: t.parameters
            })));
        }
        else {
            this._persistence.handle(event, this.sessionManager, turnIndex);
        }
        // --- Turn counter (pipeline concern, after all consumers of turnIndex) ---
        if (event.type === 'turn_start')
            this._sessionState.incrementTurn();
        if (event.type === 'turn_end')
            this._turnIndex++;
        // --- Step 4b: Post-turn hooks (async — between turns, cancellable on user input) ---
        if (event.type === 'turn_end' && this._extensions.extensionRunner) {
            await this._extensions.extensionRunner.runPostTurnHandlers(turnIndex, event.message, event.toolResults);
        }
        // --- Step 5: Retry success tracking (sync) ---
        // Resolve the retry promise only when the retried agent loop finishes
        // successfully (agent_end with no error). Resolving on message_end was a
        // race condition: session.prompt() would return while the retry loop was
        // still executing tool calls, causing print-mode to exit and kill the
        // in-flight tool execution.
        if (event.type === 'agent_end' && this._recovery.attempt > 0) {
            const lastAssistant = this._findLastAssistantInMessages(event.messages);
            if (!lastAssistant || lastAssistant.stopReason !== 'error') {
                this._recovery.onSuccess();
            }
        }
        // --- Step 6: Post-agent: stop hooks → retry → compaction (async, sequential, conditional) ---
        if (event.type === 'agent_end') {
            // --- Step 6a: Stop hooks (run before retry/compaction) ---
            const stopHookContinued = await this._runStopHooks(event.messages);
            if (stopHookContinued) {
                this._recovery.abort();
                return;
            }
            const lastAssistant = this._persistence.lastAssistantMessage;
            this._persistence.clearLastAssistant();
            if (lastAssistant) {
                const category = classifyError(lastAssistant, this._checkContextOverflow);
                if (category) {
                    const didRecover = await this._recovery.handleError(lastAssistant, category, this.settingsManager.getErrorRecoverySettings(), this._errorRecoveryActions);
                    if (didRecover)
                        return;
                }
                // Threshold compaction for non-error responses (overflow is handled by error recovery).
                // Auto-compaction now rejects on failure (timeout / model error / persist error) so
                // that heartbeat emits a compaction_failed/timeout OpEnd row to ClickHouse with the
                // real error message. The failure is already on the auto_compaction_end session event
                // by the time the rejection lands here, so the agent_end hook must not crash.
                await this._triggerCompactionIfNeeded(lastAssistant, true).catch(() => undefined);
            }
        }
    }
    /**
     * Run stop hooks from extensions after agent_end, before retry/compaction.
     * Returns true if a hook triggered continuation (caller should return early).
     */
    async _runStopHooks(messages) {
        if (!this._extensions.extensionRunner)
            return false;
        const hooks = this._extensions.extensionRunner.getStopHooks();
        if (hooks.length === 0)
            return false;
        const lastAssistant = this._findLastAssistantInMessages(messages);
        const ctx = {
            messages,
            lastAssistantMessage: lastAssistant,
            turnIndex: this._turnIndex,
            state: this.agent.state
        };
        for (const hook of hooks) {
            try {
                const result = await hook.onTurnEnd(ctx);
                if (result.action === 'continue') {
                    // Queue follow-up messages
                    const followUps = [
                        ...(result.followUpMessages ?? []),
                        ...(result.blockingErrors ?? []).map((e) => `[BLOCKING ERROR] ${e}`)
                    ];
                    for (const text of followUps) {
                        this.agent.followUp({
                            role: 'user',
                            content: [{ type: 'text', text }],
                            timestamp: Date.now()
                        });
                    }
                    // Restart agent loop (same pattern as compaction)
                    setTimeout(() => {
                        this.agent.continue().catch(() => { });
                    }, 100);
                    return true;
                }
            }
            catch (err) {
                // Report error but don't block other hooks or retry/compaction
                this._extensions.extensionRunner?.emitError({
                    extensionPath: `stop_hook:${hook.name}`,
                    event: 'agent_end',
                    error: err instanceof Error ? err.message : String(err),
                    stack: err instanceof Error ? err.stack : undefined
                });
            }
        }
        return false;
    }
    /**
     * Explicit execution limits for bounded contexts such as sub-agents.
     *
     * The main `/budget ...` product path does not use hidden settings-driven limits.
     */
    _getExecutionBudgetLimits() {
        return {
            maxTurns: this._budgetLimits?.maxTurns ?? 0,
            maxToolUses: this._budgetLimits?.maxToolUses ?? 0,
            maxTokens: this._budgetLimits?.maxTokens ?? 0
        };
    }
    /** Find the last assistant message in agent state (including aborted ones) */
    _findLastAssistantMessage() {
        const messages = this.agent.state.messages;
        for (let i = messages.length - 1; i >= 0; i--) {
            const msg = messages[i];
            if (msg.role === 'assistant') {
                return msg;
            }
        }
        return undefined;
    }
    /**
     * Subscribe to agent events.
     * Session persistence is handled internally (saves messages on message_end).
     * Multiple listeners can be added. Returns unsubscribe function for this listener.
     */
    subscribe(listener) {
        this._eventListeners.push(listener);
        // Return unsubscribe function for this specific listener
        return () => {
            const index = this._eventListeners.indexOf(listener);
            if (index !== -1) {
                this._eventListeners.splice(index, 1);
            }
        };
    }
    /**
     * Temporarily disconnect from agent events.
     * User listeners are preserved and will receive events again after resubscribe().
     * Used internally during operations that need to pause event processing.
     */
    _disconnectFromAgent() {
        if (this._unsubscribeAgent) {
            this._unsubscribeAgent();
            this._unsubscribeAgent = undefined;
        }
    }
    /**
     * Reconnect to agent events after _disconnectFromAgent().
     * Preserves all existing listeners.
     */
    _reconnectToAgent() {
        if (this._unsubscribeAgent)
            return; // Already connected
        this._unsubscribeAgent = this.agent.subscribe(this._handleAgentLoopEvent);
    }
    /**
     * Remove all listeners and disconnect from agent.
     * Call this when completely done with the session.
     */
    dispose() {
        this._disconnectFromAgent();
        this._summaryEmitter.abort();
        this._eventListeners = [];
        // Clean up collaboration resources
        this._collabSync?.destroy();
        this._collabSync = null;
        this._collabAwareness?.destroy();
        this._collabAwareness = null;
        this._collabClient?.destroy();
        this._collabClient = null;
    }
    /**
     * Wait for any in-flight suggestion generation to complete.
     * Call before process exit to avoid dangling promises.
     */
    async awaitPendingSuggestions() {
        return;
    }
    // =========================================================================
    // Read-only State Access
    // =========================================================================
    /** Full agent state */
    get state() {
        return this.agent.state;
    }
    /** Currently selected model for this session, if any. */
    get model() {
        return this.agent.state.model;
    }
    /** Current thinking level */
    get thinkingLevel() {
        return this.agent.state.thinkingLevel;
    }
    get isFastModeEnabled() {
        return this.isFastModeAvailable && this.agent.speed === 'fast';
    }
    get isFastModeAllowed() {
        return this._fastModeAllowed;
    }
    get fastModeDisabledReason() {
        return this._fastModeDisabledReason;
    }
    get isFastModeAvailable() {
        return this._fastModeAllowed && isShortcutFastModeModel(this.model);
    }
    setFastModeAllowed(allowed, reason = 'team') {
        this._fastModeAllowed = allowed;
        this._fastModeDisabledReason = allowed ? undefined : reason;
        if (!allowed)
            this.setFastModeEnabled(false);
    }
    setFastModeEnabled(enabled) {
        this.agent.speed = enabled && this.isFastModeAvailable ? 'fast' : undefined;
    }
    toggleFastMode() {
        if (!this.isFastModeAvailable) {
            this.agent.speed = undefined;
            return false;
        }
        const enabled = !this.isFastModeEnabled;
        this.setFastModeEnabled(enabled);
        return enabled;
    }
    /** Whether agent is currently streaming a response */
    get isStreaming() {
        return this.agent.state.isStreaming;
    }
    /** Current effective system prompt (includes any per-turn extension modifications) */
    get systemPrompt() {
        return this.agent.state.systemPrompt;
    }
    /** Current retry attempt (0 if not retrying) */
    get retryAttempt() {
        return this._recovery.attempt;
    }
    /** Session-scoped file uploads. */
    get fileUploads() {
        return this._fileUploads;
    }
    /**
     * Merge new file uploads into session state, deduplicating by name.
     */
    addFileUploads(files) {
        if (files.length === 0)
            return;
        this._fileUploads = mergeFileUploads(this._fileUploads, files);
    }
    /**
     * Get the names of currently active tools.
     * Delegates to ToolRegistry (single source of truth).
     */
    getToolNames() {
        return this._tools.getToolNames();
    }
    /** Get all available tools with name, description, and parameter schema. */
    get tools() {
        return this._extensions.runtimeTools;
    }
    /** Get all available tools with name, description, and parameter schema. */
    getAllTools() {
        return this._tools.getAllTools();
    }
    /**
     * Set active tools by name.
     * Only tools in the registry can be enabled. Unknown tool names are ignored.
     * Also rebuilds the system prompt to reflect the new tool set.
     * Changes take effect on the next agent turn.
     */
    replaceToolsByName(toolNames) {
        this._tools.replaceToolsByName(toolNames);
    }
    /** Switch the session into a new agent mode (tool surface, system prompt, optional name). */
    applyMode(toolNames, systemPrompt, modeName) {
        this._tools.applyMode(toolNames, systemPrompt, modeName);
    }
    /** Whether compaction (manual or auto) is currently running */
    get isCompacting() {
        return this._compaction.isCompacting;
    }
    /** Set a pending context refresh query (consumed after the current agent loop ends). */
    setPendingRefresh(refresh) {
        const progressFolder = refresh.progressFolder?.trim() || extractProgressFolder(refresh.query) || undefined;
        if (progressFolder) {
            this._setAutonomousState({ progressFolder });
        }
        this._pendingRefresh = {
            query: refresh.query,
            progressFolder,
            model: refresh.model?.trim() || undefined,
            thinkingLevel: refresh.thinkingLevel
        };
    }
    /** Consume and return the pending refresh payload, or null if none. */
    consumePendingRefresh() {
        const pending = this._pendingRefresh;
        this._pendingRefresh = null;
        if (!pending)
            return null;
        const prompt = this._autonomousState
            ? buildAutonomousRefreshPrompt(this._autonomousState, pending)
            : pending.query;
        return {
            ...pending,
            prompt
        };
    }
    /** Read-only view of the current autonomous-run state, or null if not autonomous. */
    get autonomousState() {
        return this._autonomousState;
    }
    /** Begin an autonomous run with the given task, switching the session into autonomous mode. */
    async startAutonomous(task) {
        const trimmed = task.trim();
        if (!trimmed) {
            throw new Error('Usage: /autonomous <task>');
        }
        this._setAutonomousState({
            enabled: true,
            originalUserTask: trimmed
        });
        await this.prompt(`[Skill:autonomous selected] ${trimmed}`);
    }
    /** Stage a pending session refresh (mode/model swap) to be applied on the next prompt. */
    async preparePendingRefresh(refresh) {
        const resolved = await resolvePendingRefreshOverride(refresh, this._modelRegistry);
        if (resolved.error) {
            throw new Error(resolved.error);
        }
        if (resolved.model &&
            (this.model?.provider !== resolved.model.provider || this.model?.id !== resolved.model.id)) {
            await this.setModel(resolved.model);
        }
        if (resolved.thinkingLevel) {
            this.setThinkingLevel(resolved.thinkingLevel);
        }
    }
    /** Drop any active autonomous-run state and resume normal interactive flow. */
    clearAutonomousState() {
        this._autonomousState = null;
        this._pendingRefresh = null;
    }
    /** All messages including custom types like BashExecutionMessage */
    get messages() {
        return this.agent.state.messages;
    }
    /** Current steering mode */
    get steeringMode() {
        return this.agent.getSteeringMode();
    }
    /** Current follow-up mode */
    get followUpMode() {
        return this.agent.getFollowUpMode();
    }
    /** Current session file path, or undefined if sessions are disabled */
    get sessionFile() {
        return this.sessionManager.getSessionFile();
    }
    /** Current session ID */
    get sessionId() {
        return this.sessionManager.getSessionId();
    }
    /** Current session display name, if set */
    get sessionName() {
        return this.sessionManager.getSessionName();
    }
    /** File-based prompt templates */
    get promptTemplates() {
        return this._resourceLoader.getPrompts().prompts;
    }
    // =========================================================================
    // Prompting
    // =========================================================================
    /**
     * Send a prompt to the agent.
     * - Expands file-based prompt templates by default
     * - During streaming, queues via steer() by default, or followUp() when requested
     * - Validates model and API key before sending (when not streaming)
     * @throws Error if no model selected or no API key available (when not streaming)
     */
    async prompt(text, options) {
        const expandPromptTemplates = options?.expandPromptTemplates ?? true;
        const prepared = await this._preparePromptInput(text, options?.images, {
            expandPromptTemplates,
            source: options?.source ?? 'interactive',
            commandMode: 'execute'
        });
        if (prepared.handled) {
            return;
        }
        let currentText = prepared.text;
        const currentImages = prepared.images;
        const parsedBudget = parseBudgetCommand(currentText);
        if (parsedBudget) {
            currentText = parsedBudget.task;
        }
        const expandedText = currentText;
        // If streaming, queue via steer() by default; follow-up is opt-in.
        if (this.isStreaming) {
            if (options?.streamingBehavior === 'followUp') {
                await this._queueFollowUp(expandedText, currentImages);
            }
            else {
                await this._queueSteer(expandedText, currentImages);
            }
            return;
        }
        // Validate model
        if (!this.model) {
            throw new LoginRequiredError();
        }
        // Validate API key
        const apiKey = await this._modelRegistry.getApiKey(this.model);
        if (!apiKey) {
            throw new LoginRequiredError();
        }
        // Check if we need to compact before sending (catches aborted responses).
        // See parallel call above re: swallowing — auto-compaction now rejects on failure
        // for telemetry purposes; the failure is already on the session event so the
        // pre-submit path must not abort here.
        const lastAssistant = this._findLastAssistantMessage();
        if (lastAssistant) {
            await this._triggerCompactionIfNeeded(lastAssistant, false).catch(() => undefined);
        }
        // Build messages array (custom message if any, then user message)
        const messages = [];
        this.lastPrePromptDetails = undefined;
        const prePromptContextParts = [formatRuntimePermissionBypassReminder(this.permissionPolicy)];
        let prePromptDetails;
        if (this._prePromptContext) {
            const ctx = await this._prePromptContext();
            if (ctx) {
                this.lastPrePromptDetails = ctx.details;
                prePromptDetails = ctx.details;
                prePromptContextParts.unshift(ctx.text);
            }
        }
        messages.push({
            role: 'custom',
            customType: 'prePromptContext',
            content: formatSystemReminderContext(prePromptContextParts),
            display: false,
            details: prePromptDetails,
            timestamp: Date.now()
        });
        // Add user message
        const userContent = [{ type: 'text', text: expandedText }];
        if (currentImages) {
            userContent.push(...currentImages);
        }
        const userMessageId = globalThis.crypto.randomUUID();
        messages.push({
            id: userMessageId,
            role: 'user',
            content: userContent,
            timestamp: Date.now()
        });
        // Inject any pending "nextTurn" messages as context alongside the user message
        for (const msg of this._pendingNextTurnMessages) {
            messages.push(msg);
        }
        this._pendingNextTurnMessages = [];
        // Emit before_agent_start extension event
        if (this._extensions.extensionRunner) {
            const result = await this._extensions.extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._tools.baseSystemPrompt);
            // Add all custom messages from extensions
            if (result?.messages) {
                for (const msg of result.messages) {
                    messages.push({
                        role: 'custom',
                        customType: msg.customType,
                        content: msg.content,
                        display: msg.display,
                        details: msg.details,
                        timestamp: Date.now()
                    });
                }
            }
            // Apply extension-modified system prompt, or reset to base
            if (result?.systemPrompt) {
                this.agent.setSystemPrompt(result.systemPrompt);
            }
            else {
                // Ensure we're using the base prompt (in case previous turn had modifications)
                this.agent.setSystemPrompt(this._tools.baseSystemPrompt);
            }
        }
        // Inject file upload context into system prompt when files are present
        if (this._fileUploads.length > 0) {
            const fileContext = formatFileUploadsContext(this._fileUploads);
            this.agent.setSystemPrompt(this.agent.state.systemPrompt + fileContext);
        }
        this._budgetContinuation.beginPromptRun(parsedBudget?.budgetTokens);
        const workbookDetails = this.lastPrePromptDetails;
        const promptMetadata = buildRunAgentMetadata({
            charCount: expandedText.length,
            imageCount: currentImages?.length ?? 0,
            conversationId: this.sessionManager.getSessionId(),
            workbookDetails,
            userMessageId
        });
        this._pendingImmediateRunAgentMetadata = promptMetadata;
        this._observability?.info?.({
            module: 'ui',
            event: 'prompt_submitted',
            data: promptMetadata
        });
        try {
            await this.agent.prompt(messages);
            await this._agentEventQueue.catch(() => { });
            await this._recovery.wait();
        }
        finally {
            await this._agentEventQueue.catch(() => { });
            if (this._pendingImmediateRunAgentMetadata === promptMetadata) {
                this._pendingImmediateRunAgentMetadata = undefined;
            }
        }
    }
    /** Rewrite skill commands (/skill:name args) to selected-skill markers. */
    _expandSkillCommand(text) {
        return expandSkillCommand(text, this._resourceLoader);
    }
    /** Mutate the autonomous state record, persisting it to the session manager. */
    _setAutonomousState(next) {
        const current = this._autonomousState;
        const originalUserTask = 'originalUserTask' in next ? next.originalUserTask.trim() : current?.originalUserTask;
        if (!originalUserTask) {
            return;
        }
        const progressFolder = 'progressFolder' in next ? next.progressFolder?.trim() || undefined : current?.progressFolder;
        this._autonomousState = {
            enabled: true,
            originalUserTask,
            progressFolder
        };
        this.sessionManager.appendCustomEntry(AUTONOMOUS_STATE_CUSTOM_TYPE, this._autonomousState);
    }
    /** Reload autonomous-run state from the persisted session header on resume. */
    _restoreAutonomousStateFromSession() {
        this._autonomousState = inferAutonomousStateFromEntries(this.sessionManager.getEntries());
    }
    /** Wrap a user prompt with autonomous-run framing before sending it to the model. */
    _prepareAutonomousPrompt(text) {
        if (!text.startsWith('/autonomous'))
            return text;
        const spaceIndex = text.indexOf(' ');
        const task = (spaceIndex === -1 ? '' : text.slice(spaceIndex + 1)).trim();
        if (!task)
            return text;
        this._setAutonomousState({
            enabled: true,
            originalUserTask: task
        });
        return `[Skill:autonomous selected] ${task}`;
    }
    /**
     * Queue a steering message to interrupt the agent mid-run.
     * Delivered after current tool execution, skips remaining tools.
     * Expands skill commands and prompt templates.
     * @param images Optional image attachments to include with the message
     */
    async steer(text, images) {
        const prepared = await this._preparePromptInput(text, images, {
            expandPromptTemplates: true,
            source: 'interactive',
            commandMode: 'reject'
        });
        if (prepared.handled) {
            return;
        }
        await this._queueSteer(prepared.text, prepared.images);
    }
    /**
     * Queue a follow-up message to be processed after the agent finishes.
     * Delivered only when agent has no more tool calls or steering messages.
     * Expands skill commands and prompt templates.
     * @param images Optional image attachments to include with the message
     */
    async followUp(text, images) {
        const prepared = await this._preparePromptInput(text, images, {
            expandPromptTemplates: true,
            source: 'interactive',
            commandMode: 'reject'
        });
        if (prepared.handled) {
            return;
        }
        await this._queueFollowUp(prepared.text, prepared.images);
    }
    /** Resolve raw prompt text + images into the final agent-input shape (skills, autonomous, etc.). */
    async _preparePromptInput(text, images, options) {
        let currentText = this._prepareAutonomousPrompt(text);
        let currentImages = images;
        const extensionRunner = this._extensions.extensionRunner;
        if (extensionRunner) {
            const inputResult = await extensionRunner.emitInput(currentText, currentImages, options.source);
            if (inputResult.action === 'handled') {
                return { handled: true };
            }
            if (inputResult.action === 'transform') {
                currentText = inputResult.text;
                currentImages = inputResult.images;
            }
        }
        if (options.commandMode === 'execute' && currentText.startsWith('/')) {
            const handled = await tryExtensionCommand(currentText, extensionRunner);
            if (handled) {
                return { handled: true };
            }
        }
        if (options.expandPromptTemplates) {
            currentText = this._expandSkillCommand(currentText);
            currentText = expandPromptTemplate(currentText, [...this.promptTemplates]);
        }
        const parsedSkill = parseSkillBlock(currentText);
        if (parsedSkill?.name === 'autonomous' && parsedSkill.userMessage) {
            this._setAutonomousState({
                enabled: true,
                originalUserTask: parsedSkill.userMessage
            });
        }
        if (options.commandMode === 'reject' && currentText.startsWith('/')) {
            assertNotExtensionCommand(currentText, extensionRunner);
        }
        return {
            handled: false,
            text: currentText,
            images: currentImages
        };
    }
    /** Enqueue a steering message to be delivered before the next assistant response. */
    async _queueSteer(text, images) {
        this._queueTracker.pushSteering(text);
        this.agent.steer({
            id: globalThis.crypto.randomUUID(),
            role: 'user',
            content: buildMessageContent(text, images),
            timestamp: Date.now()
        });
    }
    /** Enqueue a follow-up message to be delivered after the current turn completes. */
    async _queueFollowUp(text, images) {
        this._queueTracker.pushFollowUp(text);
        this.agent.followUp({
            id: globalThis.crypto.randomUUID(),
            role: 'user',
            content: buildMessageContent(text, images),
            timestamp: Date.now()
        });
    }
    /**
     * Send a custom message to the session. Creates a CustomMessageEntry.
     *
     * Handles three cases:
     * - Streaming: queues message, processed when loop pulls from queue
     * - Not streaming + triggerTurn: appends to state/session, starts new turn
     * - Not streaming + no trigger: appends to state/session, no turn
     *
     * @param message Custom message with customType, content, display, details
     * @param options.triggerTurn If true and not streaming, triggers a new LLM turn
     * @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
     */
    async sendCustomMessage(message, options) {
        const appMessage = {
            role: 'custom',
            customType: message.customType,
            content: message.content,
            display: message.display,
            details: message.details,
            timestamp: Date.now()
        };
        if (options?.deliverAs === 'nextTurn') {
            this._pendingNextTurnMessages.push(appMessage);
        }
        else if (this.isStreaming) {
            if (options?.deliverAs === 'followUp') {
                this.agent.followUp(appMessage);
            }
            else {
                this.agent.steer(appMessage);
            }
        }
        else if (options?.triggerTurn) {
            await this.agent.prompt(appMessage);
        }
        else {
            this.agent.appendMessage(appMessage);
            this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
            this._emit({ type: 'message_start', message: appMessage });
            this._emit({ type: 'message_end', message: appMessage });
        }
    }
    /**
     * Send a user message to the agent. Always triggers a turn.
     * When the agent is streaming, use deliverAs to specify how to queue the message.
     *
     * @param content User message content (string or content array)
     * @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
     */
    async sendUserMessage(content, options) {
        const { text, images } = normalizeUserContent(content);
        await this.prompt(text, {
            expandPromptTemplates: false,
            streamingBehavior: options?.deliverAs,
            images,
            source: 'extension'
        });
    }
    /**
     * Clear all queued messages and return them.
     * Useful for restoring to editor when user aborts.
     * @returns Object with steering and followUp arrays
     */
    clearQueue() {
        const result = this._queueTracker.clearAll();
        this.agent.clearAllQueues();
        return result;
    }
    /** Number of pending messages (includes both steering and follow-up) */
    get pendingMessageCount() {
        return this._queueTracker.pendingCount;
    }
    /** Get pending steering messages (read-only) */
    getSteeringMessages() {
        return this._queueTracker.steeringMessages;
    }
    /** Get pending follow-up messages (read-only) */
    getFollowUpMessages() {
        return this._queueTracker.followUpMessages;
    }
    /** Resource loader for skills, prompts, and context files used by this session. */
    get resourceLoader() {
        return this._resourceLoader;
    }
    /** Active collaboration client when the session is part of a collab room, otherwise null. */
    get collabClient() {
        return this._collabClient;
    }
    /** Attach or detach the collaboration client for this session. */
    set collabClient(client) {
        this._collabClient = client;
    }
    /** Sheet-engine pool currently bound to this session, or null if unbound. */
    get sheetPool() {
        return this._sheetPool;
    }
    /** Attach or detach the sheet-engine pool used by new-sheet flows. */
    set sheetPool(pool) {
        this._sheetPool = pool;
    }
    /** Awareness layer for the active collab session (peer cursors, presence), or null. */
    get collabAwareness() {
        return this._collabAwareness;
    }
    /** Attach or detach the collab awareness layer. */
    set collabAwareness(awareness) {
        this._collabAwareness = awareness;
    }
    /** Collab document sync controller for this session, or null if not in a room. */
    get collabSync() {
        return this._collabSync;
    }
    /** Attach or detach the collab document sync controller. */
    set collabSync(sync) {
        this._collabSync = sync;
    }
    /**
     * Abort current operation and wait for agent to become idle.
     */
    async abort() {
        this._recovery.abort();
        this.agent.abort();
        await this.agent.waitForIdle();
    }
    /**
     * Start a new session, optionally with initial messages and parent tracking.
     * Clears all messages and starts a new session.
     * Listeners are preserved and will continue receiving events.
     * @param options.parentSession - Optional parent session path for tracking
     * @param options.setup - Optional callback to initialize session (e.g., append messages)
     * @returns true if completed, false if cancelled by extension
     */
    async newSession(options) {
        this._fileUploads = [];
        this._budgetContinuation.reset();
        if (!options?.preserveAutonomous) {
            this.clearAutonomousState();
        }
        return executeNewSession({
            agent: this.agent,
            sessionManager: this.sessionManager,
            extensionRunner: this._extensions.extensionRunner,
            models: this._models,
            queueTracker: this._queueTracker,
            sessionFile: this.sessionFile,
            thinkingLevel: this.thinkingLevel,
            abortFn: () => this.abort(),
            disconnectFromAgent: () => this._disconnectFromAgent(),
            reconnectToAgent: () => this._reconnectToAgent(),
            clearPendingNextTurnMessages: () => {
                this._pendingNextTurnMessages = [];
            },
            parentSession: options?.parentSession,
            setup: options?.setup,
            observability: this._observability
        });
    }
    // =========================================================================
    // Model Management (delegated to ModelManager)
    // =========================================================================
    /** Switch the session's active LLM to `model`, persisting the change. */
    async setModel(model) {
        await this._models.setModel(model);
        if (!this.isFastModeAvailable)
            this.setFastModeEnabled(false);
    }
    /** Cycle to the next configured model and report the switch result. */
    async cycleModel(direction = 'forward') {
        const result = await this._models.cycleModel(direction);
        if (!this.isFastModeAvailable)
            this.setFastModeEnabled(false);
        return result;
    }
    /** Set the model's reasoning/thinking level for subsequent turns. */
    setThinkingLevel(level) {
        this._models.setThinkingLevel(level);
    }
    /** Advance to the next thinking level supported by the active model. */
    cycleThinkingLevel() {
        return this._models.cycleThinkingLevel();
    }
    /** List the thinking levels supported by the active model. */
    getAvailableThinkingLevels() {
        return getAvailableThinkingLevels(this.model);
    }
    /** True if the active model supports the extra-high thinking level. */
    supportsXhighThinking() {
        return supportsXhighThinking(this.model);
    }
    /** True if the active model exposes any thinking/reasoning configuration. */
    supportsThinking() {
        return supportsThinking(this.model);
    }
    // =========================================================================
    // Compaction (delegated to SessionCompaction)
    // =========================================================================
    // =========================================================================
    // Compaction (delegated to SessionCompaction + bridge factories)
    // =========================================================================
    /** Check compaction decision and trigger if needed. */
    async _triggerCompactionIfNeeded(msg, skipAbortedCheck) {
        return triggerCompactionIfNeeded(this._compactionTriggerDeps, msg, skipAbortedCheck);
    }
    /** Run a manual compaction pass over the conversation, optionally with custom instructions. */
    async compact(customInstructions) {
        this._disconnectFromAgent();
        await this.abort();
        try {
            if (!this.model)
                throw new Error('No model selected');
            const apiKey = await this._modelRegistry.getApiKey(this.model);
            if (!apiKey)
                throw new Error(`No API key for ${this.model.provider}`);
            return await this._compaction.runManual(this.model, apiKey, this.sessionManager.getBranch(), customInstructions, this._compactionActions);
        }
        finally {
            this._reconnectToAgent();
        }
    }
    /** Cancel any in-flight compaction run. */
    abortCompaction() {
        this._compaction.abortAll();
    }
    /** Auto-compaction is always enabled */
    get autoCompactionEnabled() {
        return true;
    }
    // =========================================================================
    // Extension Lifecycle (delegated to ExtensionLifecycle)
    // =========================================================================
    /** Bind UI/host integration callbacks for the extension runtime. */
    async bindExtensions(bindings) {
        return this._extensions.bindExtensions(bindings);
    }
    /** Reload extensions, skills, prompts, and themes; returns any non-fatal warnings. */
    async reload() {
        return this._extensions.reload();
    }
    // =========================================================================
    // Error Recovery (delegated to SessionErrorRecovery)
    // =========================================================================
    /** Cancel in-progress recovery. */
    abortRetry() {
        this._recovery.abort();
    }
    /** Whether error recovery is currently in progress */
    get isRetrying() {
        return this._recovery.isRetrying;
    }
    /** Whether auto-retry is enabled */
    get autoRetryEnabled() {
        return this.settingsManager.getRetryEnabled();
    }
    /** Toggle auto-retry setting. */
    setAutoRetryEnabled(enabled) {
        this.settingsManager.setRetryEnabled(enabled);
    }
    // =========================================================================
    // Session Management
    // =========================================================================
    /**
     * Switch to a different session file.
     * Aborts current operation, loads messages, restores model/thinking.
     * Listeners are preserved and will continue receiving events.
     * @returns true if switch completed, false if cancelled by extension
     */
    async switchSession(sessionPath) {
        this._fileUploads = [];
        this._budgetContinuation.reset();
        this.clearAutonomousState();
        const switched = await executeSwitchSession({
            sessionPath,
            agent: this.agent,
            sessionManager: this.sessionManager,
            extensionRunner: this._extensions.extensionRunner,
            models: this._models,
            modelRegistry: this._modelRegistry,
            settingsManager: this.settingsManager,
            queueTracker: this._queueTracker,
            abortFn: () => this.abort(),
            disconnectFromAgent: () => this._disconnectFromAgent(),
            reconnectToAgent: () => this._reconnectToAgent(),
            clearPendingNextTurnMessages: () => {
                this._pendingNextTurnMessages = [];
            },
            setThinkingLevel: (level) => this.setThinkingLevel(level),
            observability: this._observability
        });
        if (switched) {
            this._restoreAutonomousStateFromSession();
        }
        return switched;
    }
    /**
     * Set a display name for the current session.
     */
    setSessionName(name) {
        this.sessionManager.appendSessionInfo(name);
    }
    /**
     * Create a fork from a specific entry.
     */
    async fork(entryId) {
        return this._branches.fork(entryId);
    }
    /**
     * Navigate to a different node in the session tree.
     * Unlike fork() which creates a new session file, this stays in the same file.
     */
    navigateTree(targetId, options = {}) {
        return this._branches.navigateTree(targetId, options);
    }
    /**
     * Get all user messages from session for fork selector.
     */
    getUserMessagesForForking() {
        return this._branches.getUserMessagesForForking();
    }
    /**
     * Get session statistics.
     */
    getSessionStats() {
        return computeSessionStats(this.state, this.sessionFile, this.sessionId);
    }
    /** Estimate token usage and remaining context budget for the current conversation. */
    getContextUsage() {
        return computeContextUsage(this.model, this.messages, this.sessionManager);
    }
    // =========================================================================
    // Extension System
    // =========================================================================
    /**
     * Check if extensions have handlers for a specific event type.
     */
    hasExtensionHandlers(eventType) {
        return this._extensions.extensionRunner?.hasHandlers(eventType) ?? false;
    }
    /**
     * Get the extension runner (for setting UI context and error handlers).
     */
    get extensionRunner() {
        return this._extensions.extensionRunner;
    }
}
//# sourceMappingURL=agent-session.js.map