import { __awaiter } from "tslib";
import { validateStaticAgentProviders } from '../../config/validate-providers';
import { recordMetaEvent } from '../../state/debug-event-log';
import { transformHistoryForAgent } from '../../utils/history-transform';
import { logger } from '../../utils/logger';
import { ChatDriver, REQUEST_CONTINUATION_TOOL, } from '../chat-driver/chat-driver';
const DEFAULT_MAX_HANDOFFS = 3;
const DEFAULT_CLASSIFIER_HISTORY_LENGTH = 4;
const DEFAULT_CLASSIFIER_RETRIES = 2;
const REQUEST_CONTINUATION_DEFINITION = {
    name: REQUEST_CONTINUATION_TOOL,
    description: "Call this when you have completed your part of the task but fulfilling the user's full request requires capabilities outside your domain. Do not call this if you can complete the request yourself. Pass a plain-language description of what still needs to be done — another specialist will be selected automatically.",
    parameters: {
        type: 'object',
        required: ['summary', 'remaining_task'],
        properties: {
            summary: {
                type: 'string',
                description: 'What you found or did — passed as context to the next specialist.',
            },
            remaining_task: {
                type: 'string',
                description: 'What still needs to be done, in plain language. Used to route to the right specialist.',
            },
        },
    },
};
function isSpecialist(agent) {
    return !agent.fallback;
}
function isFallback(agent) {
    return agent.fallback === true;
}
function buildFallbackSystemPrompt(fallback, specialists) {
    const agentList = specialists.map((s) => `- ${s.name}: ${s.description}`).join('\n');
    if (typeof fallback.systemPrompt === 'string') {
        return fallback.systemPrompt.replace('{{agents}}', agentList);
    }
    if (typeof fallback.systemPrompt === 'function') {
        // Function-form fallback prompt — pass through unchanged. The `{{agents}}`
        // substitution is a string-template convenience; consumers using the
        // function form can compose the agent list themselves if they want it.
        return fallback.systemPrompt;
    }
    return `You are a helpful assistant. You cannot directly help with the user's request, but the following specialists are available:\n\n${agentList}\n\nPolitely let the user know what you can help with and invite them to rephrase their request.`;
}
/**
 * Orchestrates multiple specialist agents. Sits between `FoundationAiAssistant`
 * and `ChatDriver`, classifying each user message and routing it to the right
 * specialist — each with its own focused system prompt, tools, and primer.
 *
 * @beta
 */
export class OrchestratingDriver extends EventTarget {
    constructor(providerRegistry, agents, options = {}) {
        var _a, _b, _c, _d;
        super();
        this.providerRegistry = providerRegistry;
        this.agents = agents;
        /**
         * Aborted on driver disposal. Threaded into `AgentLifecycleContext.signal`
         * so long-running `onActivate` work can bail if the session disconnects.
         */
        this.lifecycleAbortController = new AbortController();
        /**
         * Set by `cancel()` for the duration of a `sendMessage` run. Breaks the
         * handoff loop so we don't classify or start another agent turn after the
         * user stops. Reset at the start of each `sendMessage`.
         */
        this.cancelled = false;
        /**
         * Sticky user pick from the picker (or the host's `setAgent` API). Only
         * changes on explicit user action. Survives flow completion: when a stateful
         * agent releases, the user-pin stays put and routing falls back to it on
         * the next turn.
         */
        this.pinnedAgentName = null;
        /**
         * Transient flow lock — set when a stateful agent's `onActivate` fires,
         * cleared by `releaseActiveAgent`. While non-null, routing precedence goes
         * to this agent and the picker is locked. Independent of
         * `pinnedAgentName` so completing a flow doesn't wipe the user's sticky
         * choice.
         */
        this.flowOwnerAgentName = null;
        /**
         * Per-agent restored state for a rehydrated session, keyed by agent name.
         * Primed by the host on restore; each entry is passed to that agent's
         * `onActivate` as `ctx.restored` on its next activation, then deleted so a
         * later re-activation starts fresh.
         */
        this.restoredAgentStates = {};
        this.sessionKey = (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '';
        this.maxHandoffs = (_b = options.maxHandoffs) !== null && _b !== void 0 ? _b : DEFAULT_MAX_HANDOFFS;
        this.classifierHistoryLength =
            (_c = options.classifierHistoryLength) !== null && _c !== void 0 ? _c : DEFAULT_CLASSIFIER_HISTORY_LENGTH;
        this.classifierRetries = (_d = options.classifierRetries) !== null && _d !== void 0 ? _d : DEFAULT_CLASSIFIER_RETRIES;
        // Static-name validation: walk every agent (and nested sub-agents). Any
        // `provider: '<name>'` that isn't registered, or resolves to a provider
        // without `chat()`, throws now rather than at first turn.
        validateStaticAgentProviders(agents, providerRegistry);
        // Specialists drive the classifier. `excludeFromClassifier` agents are still
        // resolvable by name (so manual pinning works) but never auto-routed.
        this.specialists = agents.filter(isSpecialist).filter((a) => !a.excludeFromClassifier);
        const fallbacks = agents.filter(isFallback);
        if (fallbacks.length > 1) {
            logger.warn('OrchestratingDriver: multiple fallback agents found — only the first will be used.');
        }
        const rawFallback = fallbacks[0];
        this.fallback = rawFallback
            ? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
        this.chatDriver = new ChatDriver(providerRegistry, {
            maxToolIterations: options.maxToolIterations,
            maxFoldOperations: options.maxFoldOperations,
            maxTurnSnapshots: options.maxTurnSnapshots,
            sessionKey: this.sessionKey,
            activityBus: options.activityBus,
        });
        // Proxy events from the shared driver
        this.chatDriver.addEventListener('history-updated', (e) => {
            this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('sub-agent-history-updated', (e) => {
            this.dispatchEvent(new CustomEvent('sub-agent-history-updated', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('sub-agent-start', (e) => {
            this.dispatchEvent(new CustomEvent('sub-agent-start', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('sub-agent-stop', (e) => {
            this.dispatchEvent(new CustomEvent('sub-agent-stop', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('interaction-start', (e) => {
            this.dispatchEvent(new CustomEvent('interaction-start', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('interaction-stop', (e) => {
            this.dispatchEvent(new CustomEvent('interaction-stop', { detail: e.detail }));
        });
        this.chatDriver.addEventListener('provider-changed', (e) => {
            this.dispatchEvent(new CustomEvent('provider-changed', { detail: e.detail }));
        });
    }
    resolveInteraction(interactionId, result) {
        this.chatDriver.resolveInteraction(interactionId, result);
    }
    getInteractionContext(interactionId) {
        return this.chatDriver.getInteractionContext(interactionId);
    }
    isBusy() {
        return this.chatDriver.isBusy();
    }
    /** Currently active provider name from the underlying ChatDriver. */
    getActiveProviderName() {
        return this.chatDriver.getActiveProviderName();
    }
    /**
     * Pins routing to a specific agent by name. While pinned, the classifier is
     * skipped and the continuation tool is hidden from the agent's tool list, so
     * the agent cannot quietly hand back to the orchestrator. Pass `null` to
     * return to automatic routing.
     */
    setPinnedAgent(name) {
        this.pinnedAgentName = name;
    }
    /**
     * Set the flow-owner lock — used to restore state from Redux on driver
     * reconstruction (agents-array changes, pop-in/pop-out). Not intended for
     * external callers to manipulate routing; that's what `setPinnedAgent` is
     * for.
     */
    setFlowOwner(name) {
        this.flowOwnerAgentName = name;
    }
    loadHistory(messages) {
        this.chatDriver.loadHistory(messages);
    }
    getRawHistory() {
        return this.chatDriver.getHistory();
    }
    /** {@inheritDoc AiDriver.compact} */
    compact() {
        return this.chatDriver.compact();
    }
    /** {@inheritDoc AiDriver.canCompact} */
    canCompact() {
        return this.chatDriver.canCompact();
    }
    /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
    getTurnSnapshots() {
        return this.chatDriver.getTurnSnapshots();
    }
    /** Delegates to the inner {@link ChatDriver} — interactions resolve there, so it holds the buffer. */
    getExternalDiagnostics() {
        return this.chatDriver.getExternalDiagnostics();
    }
    getSuggestions(history, prompt, count, allAgentInfo) {
        return __awaiter(this, void 0, void 0, function* () {
            // When pinned to a specialist, scope suggestions to that agent so the
            // proposed prompts reflect the active routing instead of the full panel.
            const pinned = this.resolvePinnedAgent();
            const candidates = pinned && isSpecialist(pinned) ? [pinned] : this.specialists;
            const agentInfo = candidates.map((s) => ({
                name: s.name,
                description: s.description,
                // Suggestions use tool names for prompt hints. Dynamic agents resolve
                // their tools per-turn against a SystemPromptContext we don't have here
                // — pass an empty list rather than invoke the factory with a fake one.
                tools: Array.isArray(s.toolDefinitions) ? s.toolDefinitions : [],
            }));
            return this.chatDriver.getSuggestions(history, prompt, count, agentInfo);
        });
    }
    sendMessage(input, attachments) {
        return __awaiter(this, void 0, void 0, function* () {
            this.cancelled = false;
            const history = this.chatDriver.getHistory();
            // Emit the user message immediately so the UI reflects it during the classify
            // round-trip — without this the chat appears frozen until classify returns.
            this.dispatchEvent(new CustomEvent('history-updated', {
                detail: [...history, { role: 'user', content: input, attachments }],
            }));
            const pinned = this.resolvePinnedAgent();
            if (!pinned)
                this.dispatchEvent(new CustomEvent('orchestrating-start'));
            let currentAgent = pinned !== null && pinned !== void 0 ? pinned : (yield this.classify(input, history, this.activeAgent));
            let isHandoff = false;
            let handoffs = 0;
            let handoffSummary = '';
            let remainingTask = '';
            while (true) {
                // Cancelled before a (next) agent turn started — e.g. during classify.
                // The chat driver appends its own "Stopped." marker when a turn it was
                // running is cancelled, so we just stop here without a duplicate.
                if (this.cancelled)
                    break;
                // oxlint-disable-next-line no-await-in-loop
                yield this.applyAgent(currentAgent);
                let result;
                if (isHandoff) {
                    const contextPrimer = handoffSummary
                        ? [{ role: 'user', content: `[Context from previous agent]: ${handoffSummary}` }]
                        : [];
                    // oxlint-disable-next-line no-await-in-loop
                    result = yield this.chatDriver.continueFromHistory(contextPrimer);
                }
                else {
                    // oxlint-disable-next-line no-await-in-loop
                    result = yield this.chatDriver.sendMessage(input, attachments);
                }
                // Release check: a stateful agent called `releaseAgent` from a terminal
                // tool handler. Fire onDeactivate, clear the pin, drop the user back to
                // classifier-mode. The LLM has already emitted its final wrap-up message
                // by the time we get here — release is purely a teardown.
                if (this.chatDriver.getAgentReleaseRequested()) {
                    // oxlint-disable-next-line no-await-in-loop
                    yield this.releaseActiveAgent();
                    break;
                }
                // Pinned agents never hand off — the continuation tool is filtered out in
                // applyAgent, but this guards against a model hallucinating a handoff result.
                if (result.reason !== 'agent-handoff' || isFallback(currentAgent) || pinned) {
                    break;
                }
                handoffs += 1;
                if (handoffs > this.maxHandoffs) {
                    this.appendInlineMessage(`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed. Please try breaking your request into smaller steps.`);
                    break;
                }
                handoffSummary = result.summary;
                remainingTask = result.remainingTask;
                isHandoff = true;
                const updatedHistory = this.chatDriver.getHistory();
                this.dispatchEvent(new CustomEvent('orchestrating-start'));
                // oxlint-disable-next-line no-await-in-loop
                currentAgent = yield this.classify(remainingTask, updatedHistory);
            }
            return { reason: 'done' };
        });
    }
    continueFromHistory(transientPrimer) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.chatDriver.continueFromHistory(transientPrimer);
        });
    }
    /** {@inheritDoc AiDriver.primeRestoredAgentState} */
    primeRestoredAgentState(snapshots) {
        this.restoredAgentStates = Object.assign({}, snapshots);
    }
    /** {@inheritDoc AiDriver.getActiveAgent} */
    getActiveAgent() {
        return this.activeAgent;
    }
    applyAgent(agent) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const previousAgent = this.activeAgent;
            const isSwitch = !previousAgent || previousAgent.name !== agent.name;
            // Record the agent transition on the debug-log timeline. A `null` `from`
            // marks the initial activation; a named `from` is a handoff between agents.
            if (isSwitch) {
                recordMetaEvent(this.sessionKey, 'agent.handoff', {
                    from: (_a = previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name) !== null && _a !== void 0 ? _a : null,
                    to: agent.name,
                    // GENC-1351: was a restored snapshot waiting for this agent at activation?
                    restored: this.restoredAgentStates[agent.name] !== undefined,
                });
            }
            // Fire lifecycle hooks around the swap — outgoing first, then incoming.
            // Both are awaited so a heavy `onActivate` (e.g. machine restore) completes
            // before the agent's first turn runs.
            if (isSwitch && (previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.onDeactivate)) {
                try {
                    yield previousAgent.onDeactivate({
                        agentName: previousAgent.name,
                        sessionKey: this.sessionKey,
                        signal: this.lifecycleAbortController.signal,
                    });
                }
                catch (e) {
                    logger.warn(`OrchestratingDriver: onDeactivate("${previousAgent.name}") threw:`, e);
                }
            }
            if (isSwitch && agent.onActivate) {
                // On a session restore, hand this agent its persisted state exactly once
                // (consumed here) so a stateful machine resumes at the right step; a later
                // re-activation of the same agent then starts fresh.
                const restored = this.restoredAgentStates[agent.name];
                if (restored !== undefined)
                    delete this.restoredAgentStates[agent.name];
                try {
                    yield agent.onActivate({
                        agentName: agent.name,
                        sessionKey: this.sessionKey,
                        previousAgentName: previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name,
                        signal: this.lifecycleAbortController.signal,
                        restored,
                    });
                }
                catch (e) {
                    logger.warn(`OrchestratingDriver: onActivate("${agent.name}") threw:`, e);
                }
            }
            const hasLifecycleHooks = !!(agent.onActivate || agent.onDeactivate);
            // Stateful agents auto-lock the flow on activation. The lock guarantees
            // the machine survives subsequent turns (the classifier would otherwise be
            // free to route away mid-flow, tearing the machine down). Release happens
            // when the agent calls `releaseAgent` from a terminal-state tool handler
            // — see the post-sendMessage check below. We write `flowOwnerAgentName`
            // rather than `pinnedAgentName` so completing a flow doesn't wipe the
            // user's sticky picker choice.
            if (isSwitch && hasLifecycleHooks && this.flowOwnerAgentName !== agent.name) {
                this.flowOwnerAgentName = agent.name;
                this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: agent.name }));
            }
            // Terminal agents do not get the cross-agent handoff tool. Four cases:
            //   • fallback     — already a leaf; handoff would loop
            //   • user-pinned  — user explicitly selected this agent; do not auto-route away
            //   • flow-owner   — a stateful flow is mid-stream; do not auto-route away
            //   • stateful     — agents with lifecycle hooks own state for the duration of
            //                    their flow. Initiating a handoff mid-flow would abandon
            //                    that state with no clean exit and dump the user into the
            //                    classifier mid-machine. Capture the tool loop until the
            //                    user (or the agent itself, via `releaseAgent`) releases.
            const isTerminal = isFallback(agent) ||
                this.pinnedAgentName !== null ||
                this.flowOwnerAgentName !== null ||
                hasLifecycleHooks;
            let agentToApply = agent;
            if (!isTerminal) {
                const declaredTools = agent.toolDefinitions;
                agentToApply = Object.assign(Object.assign({}, agent), { toolDefinitions: typeof declaredTools === 'function'
                        ? (ctx) => __awaiter(this, void 0, void 0, function* () {
                            return [
                                ...(yield declaredTools(ctx)),
                                REQUEST_CONTINUATION_DEFINITION,
                            ];
                        })
                        : [...(declaredTools !== null && declaredTools !== void 0 ? declaredTools : []), REQUEST_CONTINUATION_DEFINITION] });
            }
            if (previousAgent && previousAgent.name !== agent.name) {
                const rawHistory = this.chatDriver.getHistory();
                this.chatDriver.loadHistory([
                    ...rawHistory,
                    { role: 'system-event', content: agent.name, timestamp: new Date().toISOString() },
                ]);
            }
            this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
            this.chatDriver.applyAgent(agentToApply);
            this.activeAgent = agent;
            this.dispatchEvent(new CustomEvent('orchestrating-stop'));
            this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
        });
    }
    /**
     * Release the current stateful agent: fire `onDeactivate`, clear the
     * flow-owner lock, dispatch events so the host (and Redux) reflect the
     * unlocked state. Called automatically when a tool handler invokes
     * `context.releaseAgent`.
     *
     * The user-pin (`pinnedAgentName`) is intentionally *not* cleared — a user
     * who explicitly picked this agent should stay on it after the flow ends,
     * so the next message starts a fresh flow on the same agent rather than
     * routing through the classifier.
     */
    releaseActiveAgent() {
        return __awaiter(this, void 0, void 0, function* () {
            const agent = this.activeAgent;
            if (!agent)
                return;
            if (agent.onDeactivate) {
                try {
                    yield agent.onDeactivate({
                        agentName: agent.name,
                        sessionKey: this.sessionKey,
                        signal: this.lifecycleAbortController.signal,
                    });
                }
                catch (e) {
                    logger.warn(`OrchestratingDriver: release onDeactivate("${agent.name}") threw:`, e);
                }
            }
            this.activeAgent = undefined;
            if (this.flowOwnerAgentName !== null) {
                this.flowOwnerAgentName = null;
                this.dispatchEvent(new CustomEvent('flow-owner-changed', { detail: null }));
            }
            this.dispatchEvent(new CustomEvent('agent-released', { detail: agent }));
            this.dispatchEvent(new CustomEvent('agent-changed', { detail: undefined }));
        });
    }
    /**
     * Stop the current turn (user "stop" button). Cancels the inner chat driver's
     * in-flight request and breaks the handoff loop so no further agent turn or
     * classify runs. The driver stays usable for the next message.
     */
    cancel() {
        this.cancelled = true;
        this.chatDriver.cancel();
    }
    /**
     * Fire `onDeactivate` on the current active agent and abort any pending
     * lifecycle work. Called by the host on session teardown so machines can
     * release resources cleanly.
     */
    dispose() {
        return __awaiter(this, void 0, void 0, function* () {
            const previousAgent = this.activeAgent;
            if (previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.onDeactivate) {
                try {
                    yield previousAgent.onDeactivate({
                        agentName: previousAgent.name,
                        sessionKey: this.sessionKey,
                        signal: this.lifecycleAbortController.signal,
                    });
                }
                catch (e) {
                    logger.warn(`OrchestratingDriver: dispose onDeactivate("${previousAgent.name}") threw:`, e);
                }
            }
            this.lifecycleAbortController.abort();
            // Tear down the inner driver too, so any in-flight provider request aborts.
            this.chatDriver.dispose();
            this.activeAgent = undefined;
        });
    }
    classify(input, history, contextAgent) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            // Single-candidate short-circuits. No point asking the LLM to route
            // when there's only one viable choice. Skipped if a fallback is
            // configured — that's an explicit "escape hatch" signal from the
            // consumer, and we preserve the LLM's ability to send unrelated
            // messages there by returning -1 from select_agent.
            if (this.specialists.length === 1 && !this.fallback) {
                return this.specialists[0];
            }
            if (this.specialists.length === 0) {
                // No classifier-eligible specialists. If exactly one non-fallback
                // agent exists (typically a stateful agent flagged
                // `excludeFromClassifier`) and there's no fallback to preserve as an
                // escape hatch, route to it — this is what fixes the previously-
                // silent single-stateful-agent case. Otherwise drop to the fallback;
                // excluded specialists remain reachable via manual pin.
                if (!this.fallback) {
                    const routable = this.agents.filter((a) => !isFallback(a));
                    if (routable.length === 1) {
                        return routable[0];
                    }
                }
                return (_a = this.fallback) !== null && _a !== void 0 ? _a : { name: 'Assistant', fallback: true };
            }
            const agentList = this.specialists
                .map((a, i) => `${i}: ${a.name} — ${a.description}`)
                .join('\n');
            const recentMessages = history
                .filter((m) => m.role === 'user' || m.role === 'assistant')
                .slice(-this.classifierHistoryLength)
                .map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
                .join('\n');
            const continuationHint = contextAgent && isSpecialist(contextAgent)
                ? `\n\nThe previous turn was handled by "${contextAgent.name}". If the new message is a refinement, correction, or continuation of that work, prefer "${contextAgent.name}" — unless the topic has clearly shifted to another specialist's domain.`
                : '';
            const classifierPrompt = `You are a routing classifier. Call select_agent with the index of the best matching agent for the user message. Use -1 only if the request is entirely unrelated to every agent listed — prefer the closest match when in doubt. If the request spans multiple agents, pick the one that should act first; I will re-classify and route again once that agent is done.\n\nAgents:\n${agentList}${continuationHint}${recentMessages ? `\n\nRecent conversation:\n${recentMessages}` : ''}`;
            const routingTool = {
                name: 'select_agent',
                description: 'Select the most appropriate agent for the user message.',
                parameters: {
                    type: 'object',
                    required: ['agent_index'],
                    properties: {
                        agent_index: {
                            type: 'integer',
                            description: 'Zero-based index of the matching agent, or -1 if no agent matches.',
                        },
                    },
                },
            };
            for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
                try {
                    const options = {
                        systemPrompt: classifierPrompt,
                        tools: [routingTool],
                    };
                    // Classification is orchestrator-level, not bound to any single agent —
                    // always run against the registry default.
                    const classifierProvider = this.providerRegistry.default();
                    if (!classifierProvider.chat) {
                        logger.warn('OrchestratingDriver: default AI provider does not implement chat() — cannot classify.');
                        break;
                    }
                    // oxlint-disable-next-line no-await-in-loop
                    const response = yield classifierProvider.chat([], input, options);
                    const tc = (_b = response.toolCalls) === null || _b === void 0 ? void 0 : _b[0];
                    const index = (tc === null || tc === void 0 ? void 0 : tc.name) === 'select_agent' ? tc.args.agent_index : -1;
                    if (index >= 0 && index < this.specialists.length) {
                        return this.specialists[index];
                    }
                    // index === -1 — fall through to fallback
                    break;
                }
                catch (e) {
                    logger.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
                    if (attempt === this.classifierRetries) {
                        logger.error('OrchestratingDriver: classifier failed after all retries, using fallback');
                    }
                }
            }
            if (this.fallback)
                return this.fallback;
            // No fallback configured — emit an inline response and return first specialist as no-op
            const specialistNames = this.specialists.map((s) => s.name).join(', ');
            this.appendInlineMessage(`I'm not sure how to help with that. I can assist with: ${specialistNames}.`);
            return this.specialists[0];
        });
    }
    /**
     * Returns the agent that the current turn is locked to, if any. Flow-owner
     * (transient, set by stateful-agent activation) wins over user-pin (sticky,
     * set by picker click). `undefined` falls back to the classifier.
     *
     * Logs and returns `undefined` if a name no longer matches the agents array
     * — protects against stale state after an agents-array reassignment.
     */
    resolvePinnedAgent() {
        var _a;
        const name = (_a = this.flowOwnerAgentName) !== null && _a !== void 0 ? _a : this.pinnedAgentName;
        if (name === null)
            return undefined;
        const match = this.agents.find((a) => a.name === name);
        if (!match) {
            logger.warn(`OrchestratingDriver: pinned agent "${name}" not found in agents array — falling back to classifier.`);
            return undefined;
        }
        return match;
    }
    appendInlineMessage(content) {
        const history = this.chatDriver.getHistory();
        this.chatDriver.loadHistory([
            ...history,
            { role: 'assistant', content, timestamp: new Date().toISOString() },
        ]);
        this.dispatchEvent(new CustomEvent('history-updated', { detail: this.chatDriver.getHistory() }));
    }
}
