import type { AggregateUsage, AIProviderRegistryStatusEntry, AIProviderType, ChatAttachment, ChatConfig, ChatDriverResult, ChatInputDuringExecutionMode, ChatMessage } from '@genesislcap/foundation-ai';
import { AIProviderRegistry } from '@genesislcap/foundation-ai';
import type { Modal } from '@genesislcap/rapid-design-system';
import { GenesisElement } from '@genesislcap/web-core';
import type { SettingsModalTab } from '../components/settings-modal';
import type { AgentConfig } from '../config/config';
import { AssistantAppSettingsProvider, type AssistantAppSettingsHeading, type AssistantAppSettingsToggle } from '../provider/assistant-app-settings';
import type { VendorBudgetFigures } from '../state/ai-assistant-slice';
import type { DebugLog } from '../state/persistence/diagnostics';
import type { SessionPersistenceConfig } from '../state/persistence/session-persistence-provider';
import type { InteractionContext } from '../types/interaction-context';
import { type CostSessionModelEntry, type CostSessionRecord } from '../utils/cost-session-history';
import { type ResolvedCostHistoryConfig } from '../utils/resolve-cost-history-config';
import type { AgentPickerMode, AiAssistantAnimation, AiAssistantState, PopoutMode, SettingsBudgetRow, SubmitMessageResult, SuggestionsState } from './main.types';
/** Design-system tabs element bound to the settings modal tab bar. */
type SettingsModalTabsElement = HTMLElement & {
    activeid?: string;
};
/** Three-star AI "sparkle" mark — the conventional AI signifier; default assistant avatar. */
export declare const sparkleIconSvg = "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.5 5 C9.5 11.4 11.1 13 17.5 13 C11.1 13 9.5 14.6 9.5 21 C9.5 14.6 7.9 13 1.5 13 C7.9 13 9.5 11.4 9.5 5 Z\" fill=\"currentColor\"/><path d=\"M17 2.5 C17 5.7 17.8 6.5 21 6.5 C17.8 6.5 17 7.3 17 10.5 C17 7.3 16.2 6.5 13 6.5 C16.2 6.5 17 5.7 17 2.5 Z\" fill=\"currentColor\"/><path d=\"M17.5 15 C17.5 17 18 17.5 20 17.5 C18 17.5 17.5 18 17.5 20 C17.5 18 17 17.5 15 17.5 C17 17.5 17.5 17 17.5 15 Z\" fill=\"currentColor\"/></svg>";
/** Person silhouette — default user avatar. */
export declare const userIconSvg = "<svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z\" fill=\"currentColor\"/></svg>";
/** Genesis G-mark — opt-in assistant avatar (the default before the sparkle mark). */
export declare const genesisIconSvg = "<svg viewBox=\"-4 0 30 26\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M18.7702 23.0437C18.7702 22.4675 18.8526 21.8089 19.0995 21.3149C19.3465 20.7386 19.6758 20.2447 20.0874 19.833C20.4991 19.4214 20.993 19.0921 21.5693 18.8451C22.1456 18.5982 22.7219 18.5158 23.2981 18.5158H25.9326V11.6828H19.1819V14.3172C19.1819 15.5521 18.6879 16.7047 17.8647 17.5279C17.0414 18.3512 15.8888 18.8451 14.7363 18.9275H6.7507V6.99025H16.4651V0.157227H3.54C2.63442 0.157227 1.72884 0.568855 1.07023 1.22746C0.411628 1.88606 0 2.79165 0 3.77955V22.2205C0 23.1261 0.411628 24.0316 1.07023 24.7726C1.72884 25.4312 2.63442 25.8428 3.54 25.8428H18.7702V23.0437Z\" fill=\"currentColor\"/></svg>";
/**
 * Banner copy built from the figures a 402 actually reported, or `undefined`
 * when the proxy sent none.
 *
 * `undefined` is the meaningful return, not a fallback: paired with the
 * slice's "a re-latch without a reason keeps the existing explanation" rule, it
 * makes a figureless driver latch leave a host-supplied reason intact rather
 * than blanking it back to the default at the exact moment the wall is hit.
 *
 * Exported for the unit test that pins the formatting; not part of the element
 * API.
 *
 * @internal
 */
export declare function formatBlockedReason(budget?: Partial<Extract<ChatDriverResult, {
    reason: 'done';
}>['budget']>, vendor?: AIProviderType): string | undefined;
/**
 * Whole-number percentage of a budget consumed, for the meter (GENC-1464).
 *
 * Deliberately unclamped — spend can genuinely overshoot the cap (the last
 * allowed call may cross it), and the meter's TEXT shows the truth; only the
 * bar clamps, at the render site. The one special case is a `$0` cap, which the
 * platform treats as "no spend allowed" (not "unlimited"): it has no headroom
 * by definition, so the meter reads full rather than dividing by zero.
 *
 * Exported for the unit test that pins the arithmetic; not element API.
 *
 * @internal
 */
export declare function budgetPercentOf(figures: VendorBudgetFigures): number;
/**
 * Foundation AI Assistant component.
 *
 * @remarks
 * Register one or more AI providers via `registerAIProviders` from
 * `@genesislcap/foundation-ai`; this element resolves the `AIProviderRegistry`
 * through the DI container. Pass agent configuration via the `agents` property —
 * each agent can override which registered provider it uses via `provider:`.
 * The component creates a `ChatDriver` (single agent) or `OrchestratingDriver`
 * (multiple agents) to manage the conversation loop.
 *
 * Popout/collapse coordination uses `agenticActivityBus` topics `chat-popout` and `chat-popin` — not DOM `CustomEvent`s on this element.
 *
 * **Cost build history:** opt-in via `chatConfig.costHistory.enabled`. When enabled, each
 * session's usage is kept as exactly ONE row per session key in `localStorage` (scoped by
 * `costHistory.scope`, the element `id`, or `'default'`), upserted as spend accrues. So a
 * row is a project's lifetime usage rather than a visit to it, and the Usage tab's total is
 * the sum of the rows alone — the live session is never added on top, because the active
 * project's own row already reflects it. Hosts may call
 * {@link FoundationAiAssistant.finalizeCostSession} to flush early; it is idempotent.
 *
 * @fires chat-header-mousedown - Fired when the user presses the chat header in `popout-mode="expand"` (for drag-to-popout hosts). Bubbles and composed. detail: `ChatHeaderMouseDownDetail`
 * @fires session-cleared - Fired after the session is wiped (the lifecycle menu's Clear). The payload is captured before teardown so the host can clean up any external state the active agent was working on. Bubbles and composed. detail: `SessionClearedDetail`
 *
 * @beta
 */
export declare class FoundationAiAssistant extends GenesisElement {
    providerRegistry: AIProviderRegistry;
    appSettingsProvider: AssistantAppSettingsProvider;
    designSystemPrefix: string;
    headerTitle: string;
    /**
     * Explicit session/persistence key (GENC-1351 §5.12). When set, this is the
     * session identity — the key for the driver/store/persister/diagnostics
     * registries and the `sessionKey` passed to the persistence provider — taking
     * precedence over the DOM `id`. A host that scopes persistence per-entity
     * (e.g. one chat session per project) binds this to a stable per-entity id;
     * changing it on a **connected** element re-keys the session in place — tears
     * down the current conversation and restores the new key's — via
     * `switchSession`. Falls back to `id` when unset,
     * so existing consumers are unaffected.
     */
    sessionKey?: string;
    imageSrc?: string;
    userImageSrc?: string;
    /**
     * Inline SVG markup overriding the default assistant "sparkle" avatar. The
     * markup is sanitized (SVG profile) before rendering and uses the avatar's
     * theme colour via `currentColor`. For a raster/photo avatar set `imageSrc`
     * instead, which takes precedence. Set to `genesisIconSvg` to restore the
     * Genesis G-mark.
     */
    assistantIcon?: string;
    /** Sanitized `assistantIcon`, or the default sparkle mark; bound into the template. @internal */
    assistantIconSafe: string;
    protected assistantIconChanged(): void;
    /**
     * Inline SVG markup overriding the default user "person" avatar. The markup is
     * sanitized (SVG profile) before rendering and uses the avatar's theme colour
     * via `currentColor`. For a raster/photo avatar set `userImageSrc` instead,
     * which takes precedence.
     */
    userIcon?: string;
    /** Sanitized `userIcon`, or the default person mark; bound into the template. @internal */
    userIconSafe: string;
    protected userIconChanged(): void;
    placeholder: string;
    /**
     * Controls the pop-out button shown next to the settings cog.
     * "expand" — in bubble, expands to layout panel (fires chat-popout).
     * "collapse" — in layout panel, collapses back to bubble (fires chat-popin).
     * Absent — no button (default standalone behaviour, no breaking change).
     */
    popoutMode?: PopoutMode;
    /**
     * Agent configuration. Each user message is routed to the appropriate specialist
     * based on intent. With a single entry, orchestration is skipped.
     */
    agents?: AgentConfig[];
    chatConfig: ChatConfig;
    /**
     * Optional session persistence (GENC-1351). Off by default — set `enabled`
     * and a `provider` (e.g. a `WebStorageSessionProvider`) to have the assistant
     * autosave on idle and auto-restore the conversation on page reload. Exposed
     * as a top-level property (like `agents`/`providerRegistry`) rather than via
     * `chatConfig`, since the provider type lives in this package while
     * `ChatConfig` lives in `foundation-ai`.
     */
    persistence: SessionPersistenceConfig;
    /**
     * Resolved agent picker mode from `chatConfig.picker.mode`. Defaults to
     * `'disabled'`.
     */
    get agentPicker(): AgentPickerMode;
    /**
     * Whether the built-in composer (input row + attachment chips) is hidden by
     * the host's `chatConfig.ui.hideComposer` setting. Used by the template to
     * gate those blocks. Marked `@volatile` because the `'while-splash'` branch
     * reads `showingSplash` only conditionally, so FAST cannot rely on cached
     * dependency tracking.
     */
    get composerHiddenByConfig(): boolean;
    debugStateFactory?: () => unknown;
    /** When set, enables Redux DevTools for this instance's session store. */
    debugRedux: boolean;
    /**
     * Pointer to this session's redux store. `@observable` (GENC-1351 §5.12) so that
     * **swapping** it — the in-place session switch/re-key (`switchSession`) —
     * forces every store-backed getter binding to re-evaluate against the NEW store.
     * Without this, FAST's per-store slice tracking would leave the view subscribed
     * to the old store after a swap and it wouldn't re-render. (A same-store reset via
     * redux actions re-renders without this; a store *swap* needs it.)
     */
    private _sessionRef?;
    get messages(): ChatMessage[];
    set messages(value: ChatMessage[]);
    get state(): AiAssistantState;
    set state(value: AiAssistantState);
    /** Whether a state value counts as "busy" — a turn is active. */
    private isBusyState;
    /**
     * True while a turn is active — `loading` or `cancelling` (a stop was
     * requested but the turn is still winding down). The single "is a turn
     * running" predicate; UI gates use this so `cancelling` behaves like
     * `loading` everywhere. `@volatile` so the template re-evaluates it when the
     * store-backed `state` changes.
     */
    get busy(): boolean;
    /**
     * True when a new send must be refused: a turn is running (`busy`), a page-reload
     * restore is loading history that would clobber it (`restoring`), a manual
     * compaction is rewriting history (`compacting`), or a backend condition has
     * locked the assistant outright (`blocked` — e.g. an exhausted AI budget).
     * Mirrors the composer's `?disabled` gate so a **programmatic**
     * `send`/`submitMessage` (e.g. a host's custom input while the built-in composer
     * is hidden) can't slip past it and have its message wiped when the
     * restored/compacted history lands (GENC-1351 §6).
     */
    private get sendBlocked();
    /**
     * Why a programmatic send was refused, for `submitMessage`'s `errors`. A
     * backend block is called out distinctly because — unlike the transient
     * "busy" cases — waiting and retrying will never clear it, and a caller
     * looping on "Assistant is busy" would spin forever.
     */
    private sendRefusalReason;
    /**
     * Re-runs `agentsChanged` if the live `agents` array no longer matches the
     * fingerprint of the currently installed driver. Used to apply swaps that
     * landed during a turn (and were skipped by the `isBusy()` guard) once the
     * driver returns to idle.
     */
    private applyDeferredAgentsSwap;
    /**
     * Apply a `session-key` re-key that arrived mid-turn and was deferred by
     * `switchSession`'s busy guard (GENC-1351 §5.12). Run at each point the driver
     * returns to idle, BEFORE `applyDeferredAgentsSwap` — a switch rebuilds the
     * driver against the current agents, so a pending agents-swap then no-ops.
     */
    private applyDeferredSessionSwitch;
    /**
     * Focus the main chat input if the user is still engaged with the assistant.
     * Deferred to the next frame so any conditional re-render (e.g. unhiding the
     * input row when an agent's `chatInputDuringExecution: 'hidden'` execution
     * ends) has committed before we call `.focus()`.
     *
     * `rapid-text-area` does not delegate focus to its inner native `<textarea>`,
     * so we drill into its shadow root and focus the textarea directly.
     */
    private maybeAutoFocusChatInput;
    get activeAgent(): Omit<AgentConfig, 'toolHandlers'> | undefined;
    set activeAgent(value: AgentConfig | undefined);
    get suggestionsState(): SuggestionsState;
    set suggestionsState(value: SuggestionsState);
    /** Current user-facing toggle state for tool call visibility. */
    get showToolCalls(): boolean;
    set showToolCalls(value: boolean);
    /** Current user-facing toggle state for thinking step visibility. */
    get showThinkingSteps(): boolean;
    set showThinkingSteps(value: boolean);
    /** Current user-facing toggle state for narration (interstitial prose) visibility. */
    get showNarration(): boolean;
    set showNarration(value: boolean);
    /** Current user-facing toggle state for agent switch indicator visibility. */
    get showAgentSwitchIndicator(): boolean;
    set showAgentSwitchIndicator(value: boolean);
    /** Currently enabled animations. */
    get enabledAnimations(): AiAssistantAnimation[];
    set enabledAnimations(value: AiAssistantAnimation[]);
    /**
     * Whether the agent picker slide-out panel is open. Lives on the session
     * store so the bubble-dialog and popout-panel instances stay in sync.
     */
    get agentPickerOpen(): boolean;
    set agentPickerOpen(value: boolean);
    /**
     * Whether the lifecycle (session) menu slide-out panel is open. Like the
     * agent picker, lives on the session store so bubble/popout stay in sync.
     */
    get sessionMenuOpen(): boolean;
    set sessionMenuOpen(value: boolean);
    /**
     * Whether a manual compaction is in flight (GENC-1351 §5.7). On the session
     * store (not element-local) so the "Compacting…" view + input-disable stay in
     * sync across pop-in/out, like the panel flags.
     */
    get compacting(): boolean;
    set compacting(value: boolean);
    /**
     * Whether a page-reload restore is in flight (GENC-1351 §6). On the session
     * store (like `compacting`) so the "Restoring…" view + input-disable stay in
     * sync across pop-in/out. Gating input while true prevents a mid-load send from
     * being clobbered when the restored history loads.
     */
    get restoring(): boolean;
    set restoring(value: boolean);
    /**
     * Whether the assistant is blocked by a backend condition and cannot send —
     * today, an exhausted AI-spend budget (GENC-1464). While true the composer is
     * disabled, `send`/`submitMessage` refuse, suggestions stop being fetched, and
     * a persistent banner (`part="blocked-banner"`) sits above the composer
     * explaining why. The transcript stays visible and scrollable throughout —
     * unlike `compacting` and `restoring`, this does not replace the conversation.
     *
     * **Latched.** Nothing in the element clears it: not a new turn, not "Clear"
     * / "New chat", not a pop-in/out. It stays set until the host writes
     * `false` — because nothing the user can do inside the assistant refills a
     * budget. Hosts typically set it from their own pre-flight budget check on
     * mount, and clear it once a later check shows headroom again.
     *
     * The driver also latches it automatically when a turn ends with the
     * `'budget-exhausted'` failure reason — off the `tool-loop-end` activity bus
     * (which also covers a sub-agent wall, a turn this element did not start, and
     * an element swapped in mid-turn) and off the driver's return value (which
     * covers a wall hit during multi-agent classification, where no tool loop ran
     * and so no bus event fires). So a host that does no pre-flight at all still
     * gets a correct locked UI the moment the first 402 lands. The bus topic is
     * tab-scoped, so a wall hit in a popped-out window latches the main window
     * too — correct for a shared spend cap.
     *
     * **Lifetime — read this before relying on it as your source of truth.** The
     * latch is in-memory and per-`stateKey`:
     *
     * - It does **not** survive `switchSession`. Switching away tears the outgoing
     *   session's store down entirely, so switching back yields a fresh, unblocked
     *   store.
     * - It does **not** survive a page reload. It is deliberately absent from the
     *   persisted session snapshot: the snapshot is long-lived, so a persisted
     *   latch would outlive an out-of-band budget raise with no in-element way to
     *   clear it — a stale lock is worse than re-deriving the state.
     *
     * The durable source of truth is therefore the **host's pre-flight**, which
     * should run on mount and on every session switch. See
     * `docs/migration-GENC-1464.md` §"How to adopt", Option B.
     *
     * @beta
     */
    get blocked(): boolean;
    set blocked(value: boolean);
    /**
     * The distinct vendors the registry can currently reach, from the provider
     * statuses this element already loads on connect and refreshes on every
     * observable-registry change.
     *
     * This is the element's answer to "which vendor would the next turn use" — a
     * question that has **no** correct answer and deliberately gets no API. The
     * provider is resolved per turn AND per agent: `activeProviderInput` may be an
     * async function of the turn's context, an orchestrated turn picks its agent
     * with an LLM `classify()` call, and sub-agents resolve their own providers.
     * Any pre-turn "peek" would therefore be a guess that is wrong precisely on the
     * multi-agent hosts per-vendor budgets exist for.
     *
     * Asking instead which vendors are REACHABLE is answerable, synchronous, and
     * free — and it is enough: the composer must stay live while any reachable
     * vendor has headroom, and must lock when none does. It also makes vendor-switch
     * recovery a derivation rather than a mutation: a host that swaps its registry
     * to another vendor fires the observable, the statuses reload, the walled vendor
     * drops out of this set, and `blocked` goes false with every latch left intact.
     *
     * @beta
     */
    get reachableVendors(): readonly AIProviderType[];
    /**
     * Vendors currently walled by the AI-spend budget, in the order they were
     * walled. Empty for a host that never adopts the per-vendor API.
     *
     * May include a vendor this registry cannot reach — either because the registry
     * moved on after the wall was latched, or because a 402's
     * `otherVendorAvailable: false` walls every vendor the proxy meters (which is
     * what that verdict is a statement about; see
     * {@link FoundationAiAssistant.latchBlockedFrom}). Neither costs anything
     * internally — {@link FoundationAiAssistant.blocked} asks only about the
     * reachable set, and the banner reads the reachability-filtered
     * {@link FoundationAiAssistant.relevantBlockedVendors} — but a host rendering
     * this list itself should filter it against its own registry.
     *
     * Read-only on purpose: a settable array would let a host write a partial list
     * and silently orphan the per-vendor banner copy.
     * {@link FoundationAiAssistant.setVendorBlocked} is the write path.
     *
     * @beta
     */
    get blockedVendors(): readonly AIProviderType[];
    /**
     * Whether this specific vendor's budget is walled — regardless of whether any
     * other vendor still has headroom.
     *
     * @beta
     */
    isVendorBlocked(vendor: AIProviderType): boolean;
    /**
     * Wall (or release) one vendor's budget, optionally with banner copy for it.
     * The per-vendor mirror of {@link FoundationAiAssistant.setBlocked}.
     *
     * Walling a vendor does **not** on its own disable the composer: while another
     * reachable vendor has headroom the assistant stays usable and the banner tells
     * the user to switch. Only when every reachable vendor is walled does `blocked`
     * become true.
     *
     * `reason` is composed into the banner sentence for that vendor, **however many
     * vendors are walled** — see {@link FoundationAiAssistant.effectiveBlockedReason}.
     * It is per-vendor copy, not a whole-banner override; {@link FoundationAiAssistant.blockedReason}
     * is the override. Omitting it keeps whatever explanation that vendor already
     * carried, so a driver latch landing after a host one cannot blank it.
     *
     * `'none'` is rejected with a warning rather than accepted: it is the "no
     * provider configured" sentinel, not a vendor. Latching it would put the raw
     * sentinel in the banner ("none's AI usage limit is reached") and could never
     * be undone by derivation, because `'none'` never appears in
     * {@link FoundationAiAssistant.reachableVendors} — so `blocked` could never
     * become derivable from it either. Use {@link FoundationAiAssistant.setBlocked}
     * for a vendor-agnostic block.
     *
     * @beta
     */
    setVendorBlocked(vendor: AIProviderType, blocked: boolean, reason?: string | null): void;
    /**
     * One warning per element for garbage fed to
     * {@link FoundationAiAssistant.setVendorBudget}. Once, not per call, because
     * the documented feed re-runs after every turn — a host bug would otherwise
     * repeat the same warning on every send for the life of the page.
     */
    private vendorBudgetWarningIssued;
    private warnVendorBudgetOnce;
    /**
     * Feed (or clear) one vendor's AI-spend figures for the settings-modal budget
     * meter — the numeric companion to
     * {@link FoundationAiAssistant.setVendorBlocked}, and like it a host-driven
     * write: the element meters nothing itself.
     *
     * The meter renders a row for exactly the vendors that currently HAVE
     * figures. An **unlimited** vendor (the budget endpoint reports
     * `unlimited: true` / `limitUsd: null`) is expressed by never feeding it —
     * there is deliberately no "unlimited" figure shape, because a bar with no
     * cap has nothing truthful to fill to. `spentUsd` MAY exceed `budgetUsd`
     * (the last allowed call can overshoot the cap): the row's text shows the
     * real figures and only the bar clamps.
     *
     * Returns `false` — and warns once, not per call — instead of throwing when
     * `vendor` is not a budgeted vendor (`BUDGETED_VENDORS`, the proxy's own
     * metered list) or either figure is not a finite number `>= 0`. Invalid input
     * changes no state. Idempotent: re-feeding a vendor's current figures is a
     * no-op, so hosts can re-feed unconditionally after every turn.
     *
     * `false` ALSO means "not stored, because the element is not ready yet": a
     * feed that lands before the element has its session store (a host calling
     * ahead of the first append, or during a pop-out remount) stores nothing and
     * says so, rather than reporting a write that never happened. That case is
     * transient and needs no host handling — the documented per-turn re-feed is
     * what recovers it — so it is deliberately NOT warned about; the warn-once
     * channel is for garbage input only.
     *
     * `figures` is nullish-checked at run time, so `undefined` behaves exactly
     * like `null` and CLEARS that vendor's row. Untyped JS hosts feed this
     * straight from `GET /api/budget`, where a per-vendor lookup that misses
     * yields `undefined`, not `null` — and "no figures" coherently means "no
     * row". The `| null` parameter type is unchanged for typed callers; the
     * runtime is deliberately the more forgiving of the two.
     *
     * The intended feed is the platform budget endpoint:
     *
     * ```ts
     * // `vendors` is an OBJECT keyed by vendor id, not an array.
     * const { vendors } = await (await fetch('/api/budget')).json();
     * for (const v of Object.values(vendors)) {
     *   if (v.unlimited || v.limitUsd == null) {
     *     assistantEl.setVendorBudget(v.vendor, null); // clear stale figures after a cap is lifted
     *     continue;
     *   }
     *   assistantEl.setVendorBudget(v.vendor, { budgetUsd: v.limitUsd, spentUsd: v.spentUsd });
     * }
     * ```
     *
     * Re-run it on mount and after each turn. Scope and lifetime follow the rest
     * of the session slice (per `stateKey`, survives pop-in/out and "Clear" /
     * "New chat") — except that the figures are NOT persisted to the session
     * snapshot: the server is authoritative and the host re-feeds at boot, so a
     * persisted copy could only show stale dollars after a reload.
     *
     * @beta
     */
    setVendorBudget(vendor: string, figures: {
        budgetUsd: number;
        spentUsd: number;
    } | null): boolean;
    /**
     * When budget figures last arrived — from the host's {@link FoundationAiAssistant.setVendorBudget}
     * or the wall latch's own feed — as epoch ms; `null` until the first feed.
     *
     * ELEMENT state, not slice state, on purpose: the reducer is idempotent by
     * value (an unchanged re-feed must not re-render the meter rows), but "when
     * was this fetched" must move on every feed, changed figures or not — a
     * refresh that confirms the same dollars is still a refresh. Only the
     * toolbar label observes this, so a stamp re-renders one span, not the rows.
     */
    budgetMeterLastFedAt: number | null;
    /** "Updated HH:MM:SS" for the meter toolbar, or `undefined` before any feed. */
    get settingsBudgetUpdatedLabel(): string | undefined;
    /**
     * Ask the host to re-fetch budget figures now (GENC-1464 refresh affordance).
     *
     * The element cannot fetch — the meter is host-fed by design — so this emits
     * `budget-refresh-requested` and the host answers with the same fetch that
     * already feeds {@link FoundationAiAssistant.setVendorBudget} (Create's handler coalesces in-flight
     * fetches, so a spammed button costs one request). Fired by the meter's
     * refresh button and on settings-modal open, so the figures are fresh exactly
     * when someone is looking at them. Fires unconditionally: a host without a
     * listener simply keeps the per-turn cadence.
     */
    requestBudgetRefresh(): void;
    /** Whether this vendor's wall came from the sweep alone — see the slice's `sweptVendors`. */
    private isVendorSwept;
    /**
     * The walled vendors the user can still be routed to — i.e.
     * {@link FoundationAiAssistant.blockedVendors} narrowed to the reachable set,
     * which is the only set the banner may name.
     *
     * A wall the registry can no longer reach is not news: it cannot be hit, and
     * naming it puts a vendor in front of the user that is not theirs. The concrete
     * failure this exists to stop: a host ships Anthropic-only, Anthropic walls, the
     * host swaps its registry to Gemini, Gemini walls — and the banner reads "AI
     * usage limits are reached for Anthropic and Gemini" to a user who has never
     * had an Anthropic key. The spurious second name also flips the copy onto the
     * plural branch, so even the closing sentence is wrong.
     *
     * When nothing is reachable YET (the statuses are still loading — the exact
     * window the pre-flight and the cross-tab bus latch into), the fallback keeps
     * every wall for LOCKING (`blocked` still derives true) but names only the
     * vendors walled by a REFUSAL, dropping the ones the `otherVendorAvailable`
     * sweep added. A refusal is a fact about a vendor this user just used; a sweep
     * entry is the proxy's headroom verdict about a vendor the host may not even
     * ship — naming it here reproduced the exact failure above from the other
     * direction ("Gemini's AI usage limit is reached" to an Anthropic-only user),
     * and flipped the copy onto the plural branch with it. Self-corrects when the
     * statuses land: from then on reachability, not provenance, decides.
     *
     * @internal
     */
    private get relevantBlockedVendors();
    /**
     * Whether the blocked banner has anything to say — a vendor-agnostic block, or
     * a partial wall that the NEXT send would actually hit.
     *
     * Partial exhaustion is announced only while the ACTIVE vendor (the registry
     * default the next send resolves to) is among the walled ones. A user who has
     * switched to a vendor with headroom has already acted on the advice —
     * keeping the banner up past that point nagged about a vendor they just left
     * (GENC-1464 tester feedback), while switching BACK to the walled vendor
     * brings the banner straight back. Two conservative fallbacks keep it
     * fail-informative: while the statuses are still loading the active vendor is
     * unknowable, so any reachable-and-walled vendor still announces; and a
     * vendor-agnostic block announces always, exactly as before.
     *
     * Reads the reachability-filtered list for the same reason the copy does — a
     * host that has swapped its registry away from the walled vendor has nothing
     * left to announce, and would otherwise get a banner that falls through to the
     * generic default copy over a perfectly usable composer.
     *
     * @internal
     */
    get bannerVisible(): boolean;
    /**
     * Whether a suggestions fetch would hit a wall.
     *
     * Unlike a chat turn, this one CAN be resolved exactly: both suggestion paths
     * go to the registry **default** and never to a per-agent override
     * (`ChatDriver.getSuggestions` calls `providerRegistry.default()`, and
     * `OrchestratingDriver.getSuggestions` just delegates to it). So the vendor a
     * suggestions call would use is knowable, and this is the one place in the
     * feature where that is true.
     *
     * Same rationale as the guard it replaces: a doomed suggestions call burns a
     * `suggestions.failed` meta event and parks a raw transport error in
     * `suggestionsState` for hosts to find.
     *
     * @internal
     */
    get suggestionsBlocked(): boolean;
    /**
     * The vendor the NEXT send resolves to — the registry default's provider, as
     * reported through `providerStatuses`. `undefined` while the statuses are
     * still loading (or when the default reports the `'none'` sentinel), which
     * every consumer treats conservatively.
     *
     * @internal
     */
    private get activeVendor();
    /**
     * Explanation shown in the blocked banner, or `null` for the element's default
     * copy. Only meaningful while {@link FoundationAiAssistant.blocked} is true;
     * setting `blocked = false` clears it.
     *
     * Writable, and symmetric with every other store-backed accessor on this
     * class: writing it re-latches with the CURRENT `blocked` value, so it changes
     * the copy without disturbing the flag. Assigning `null` clears the
     * explanation while staying blocked (the banner falls back to the default
     * copy). {@link FoundationAiAssistant.setBlocked} remains the way to write
     * both in one atomic action.
     *
     * @beta
     */
    get blockedReason(): string | null;
    set blockedReason(value: string | null);
    /**
     * Latch the backend block off a turn outcome. The single decision point for
     * both latch sites (the `tool-loop-end` bus subscription and `send()`'s
     * return-value check), so the rule cannot diverge between them.
     *
     * Idempotent and one-way **per vendor**: it never unblocks, and it never
     * re-writes an existing wall. That guard is load-bearing, not just an
     * optimisation — the bus fires from the driver's `finally`, i.e. BEFORE
     * `sendMessage()` resolves, so a host subscribed to `tool-loop-end` (what the
     * migration guide's Option C recommends) sets its own detailed reason and the
     * return-value latch would otherwise land a moment later and blank it.
     *
     * The guard being per-vendor rather than global is the whole difference. A
     * single global "already blocked, do nothing" would swallow a second vendor's
     * wall — so a session walled on Anthropic could never record that Gemini went
     * too, and under a mixed registry an Anthropic-only wall would lock a composer
     * that Gemini could still serve.
     *
     * **Which field is authoritative**, in order:
     *
     * 1. `budget.vendorLabel` — stamped by the transport that was actually refused,
     *    so it can never be stale. But it is a static per-transport string, so a
     *    white-labelled or multiplexing gateway fronting several upstreams leaves
     *    it unclaimed by any vendor.
     * 2. `budget.vendor` — which the driver resolved from the label where it could,
     *    and otherwise from the proxy's own `vendor` field on the 402. It is
     *    therefore NOT simply `vendorLabel` normalised, and the two can disagree;
     *    that fallback is the only attribution on offer for the gateway case above.
     * 3. `vendorHint` — the bus detail's top-level vendor, i.e. the driver's
     *    last-resolved provider. Last resort because it CAN be stale: an
     *    orchestrated turn classifies against the registry default, a provider the
     *    chat driver may never have resolved, so its last-resolved provider there is
     *    the previous turn's vendor or nothing at all.
     *
     * A wall that names no recognised vendor falls back to the vendor-agnostic
     * block — fail-safe, and identical to the pre-per-vendor behaviour.
     *
     * **`otherVendorAvailable: false` is server-known truth and outranks every
     * inference made here.** The proxy meters the pots, so only it can say whether
     * anything else has headroom; this element can only observe which vendors the
     * registry can REACH, which says nothing about their remaining spend. So when
     * the proxy says no, every other *budgeted* vendor is walled in the same pass.
     * That is what makes `blocked` derive true — locking the composer on the first
     * response instead of after a second doomed turn — and what empties the "free"
     * set the banner would otherwise have advised switching to.
     *
     * The sweep runs over `BUDGETED_VENDORS`, deliberately **not** over
     * {@link FoundationAiAssistant.reachableVendors}. Reachability is loaded
     * asynchronously (`activateSession` kicks off `loadProviderStatuses()` and does
     * not await it), so a wall landing before the statuses resolve would have swept
     * an empty set — silently discarding the one fact on the 402 the client cannot
     * re-derive, with nothing to re-run it when the statuses arrived. Worse, it let
     * the verdict effectively un-latch: the single wall derived `blocked` only
     * while the reachable set was empty, so the moment the statuses landed the
     * composer came back to life against a budget the proxy had already said was
     * gone. Sweeping the metered vendor list instead makes the verdict independent
     * of load order and of registry membership, which is exactly what it is: a
     * statement about the proxy's pots, not about this user's registry.
     *
     * Walling a vendor the registry cannot reach costs nothing: `blocked` only asks
     * whether every REACHABLE vendor is walled, and the banner reads
     * {@link FoundationAiAssistant.relevantBlockedVendors}, which filters the walls
     * back down to the reachable set before naming any of them.
     *
     * One boundary remains on the sweep: it walls only vendors the proxy meters,
     * enforced structurally by iterating `BUDGETED_VENDORS` itself rather than
     * filtering a wider set through a predicate. Chrome runs on-device with no pot
     * to exhaust, so "no other vendor has budget" is not a statement about it, and
     * "Switch to Chrome to keep going." stays honest advice.
     *
     * The sweep runs last but sits OUTSIDE the per-vendor idempotence guard, and
     * both halves of that matter. Last, so the vendor that actually refused this
     * turn heads the wall order the multi-vendor banner reads. Outside the guard,
     * because the verdict can arrive on a LATER 402 for a vendor that is already
     * walled — turn 1 walls Anthropic while Gemini still has headroom, turn 2 walls
     * it again and reports that Gemini has since gone too — and an early return
     * would drop exactly the news the second turn was there to deliver.
     *
     * @param reason - the turn's failure reason; anything but `'budget-exhausted'` is ignored.
     * @param ref - session store to write through; defaults to the live one. `send()`
     *   passes the ref it captured before its awaits, since a lifecycle event during the
     *   turn may already have cleared `_sessionRef`.
     * @param budget - what the 402 reported: figures when the proxy sent any, the
     *   refusing vendor, and its `otherVendorAvailable` verdict.
     * @param vendorHint - the `tool-loop-end` detail's top-level vendor, used only
     *   when the budget payload names none.
     *
     * @internal
     */
    private latchBlockedFrom;
    /**
     * Block or unblock the assistant, optionally with custom banner copy.
     * Equivalent to the {@link FoundationAiAssistant.blocked} setter, plus the
     * reason in one atomic write.
     *
     * Blocking writes the **vendor-agnostic** block — what a host with one budget
     * pot means — and does not populate
     * {@link FoundationAiAssistant.blockedVendors}. Unblocking clears the
     * per-vendor walls as well, so a host asserting "the wall is gone" after its
     * own pre-flight cannot be silently overruled by a driver latch it never saw.
     *
     * @beta
     */
    setBlocked(blocked: boolean, reason?: string | null): void;
    /**
     * Copy shown in the blocked banner, in ascending specificity:
     *
     * 1. the host's `blockedReason` — a whole-banner override, unchanged;
     * 2. copy composed from the walled vendors (their per-vendor reasons when set,
     *    otherwise their names) plus the action that is actually available;
     * 3. the default budget-exhaustion message.
     *
     * The action clause is computed here rather than stored, because whether
     * "switch vendor" or "contact your administrator" is honest depends on whether
     * another reachable vendor still has headroom — which changes when the registry
     * changes, long after the wall was latched. Telling a user to switch to a
     * vendor that is also exhausted is worse than saying nothing.
     *
     * The `free` set is a client-side inference — "reachable and not known to be
     * walled" — and it is only ever allowed to be one. Where the proxy has told us
     * otherwise (`otherVendorAvailable: false`) the latch has already walled those
     * vendors, so they are not in `free` here and no "switch to X" clause can be
     * composed from them. Both lists are filtered by reachability, so the copy
     * names only vendors this user's registry can actually route to.
     *
     * **Per-vendor copy is composed however many vendors are walled**, which is
     * what {@link FoundationAiAssistant.setVendorBlocked} and
     * `docs/migration-GENC-1464.md` both promise. It previously reached only the
     * single-vendor branch, so a host that had carefully set copy for each of its
     * vendors watched all of it vanish the moment a second one walled — replaced by
     * generic copy, at the exact moment the situation got worse.
     *
     * The compact list is the DEFAULT shape for several walls, now carrying each
     * vendor's figures: "AI usage limits are reached for Gemini ($62.34 of $50.00)
     * and Anthropic ($31.10 of $30.00)." A driver latch stores no copy of its own
     * (it stores the figures, which this composes from), so that shape is what the
     * real budget flow produces. As soon as any walled vendor carries HOST copy,
     * which cannot be folded into a list item, the branch lists one statement per
     * vendor instead — falling back to composed copy for the ones that have none, so
     * the sentence set stays complete rather than silently naming a subset.
     *
     * @internal
     */
    get effectiveBlockedReason(): string;
    /**
     * Copy the driver writes into the TRANSCRIPT when a turn hits the budget wall,
     * or `undefined` to let the driver compose it per wall.
     *
     * A set `blockedReason` still wins — a white-labelled host must not read its
     * own explanation in the banner and shipped copy directly below it, and the
     * vendor-agnostic latch's `formatBlockedReason(budget)` form stays advice-free
     * exactly as before.
     *
     * `undefined` (the usual case) is NOT the old `DEFAULT_BUDGET_EXHAUSTED_MESSAGE`
     * fallback: it hands composition to the driver, which builds the bubble from
     * EACH wall's own 402 — naming the exhausted vendor and advising a switch only
     * when that refusal's `otherVendorAvailable` positively vouched for headroom
     * elsewhere. That is immune to the staleness this getter used to guard against
     * by freezing the copy: construction-time advice could outlive its truth
     * (`getOrCreateDriver` re-runs on an agents swap and would have baked the
     * sentence into every later bubble), but per-wall advice is derived from the
     * refusal it annotates and dies with the turn. Forcing the default here was
     * also what made the driver's composed bubble unreachable in every real
     * mount — the banner advised switching while the bubble under it never did
     * (the GENC-1464 tester finding, root cause).
     */
    private get transcriptBudgetExhaustedMessage();
    /**
     * Name of the agent the user has pinned via the agent picker. `null` means
     * automatic routing (Auto). Persisted on the session store, so it survives
     * pop-in/pop-out but resets on page refresh.
     *
     * Only changes on explicit user action (picker click or host's `setAgent`).
     * The orchestrator's auto-pin on stateful activation writes
     * `flowOwnerAgentName` instead, so finishing a guided flow doesn't wipe
     * the user's sticky pick.
     */
    get pinnedAgentName(): string | null;
    set pinnedAgentName(value: string | null);
    /**
     * Name of the agent that currently owns a guided flow. Set by the
     * orchestrator when a stateful agent's `onActivate` fires, cleared when the
     * agent calls `releaseAgent` from a terminal-state tool handler. While
     * non-null, routing precedence goes to this agent and the picker is locked.
     *
     * Independent of `pinnedAgentName`: a flow can complete and release
     * without disturbing the user's sticky picker choice.
     */
    get flowOwnerAgentName(): string | null;
    set flowOwnerAgentName(value: string | null);
    get liveSubAgentTrace(): ChatMessage[];
    set liveSubAgentTrace(value: ChatMessage[]);
    get liveSubAgentName(): string | null;
    set liveSubAgentName(value: string | null);
    /**
     * In-flight per-call chat-input overrides pushed by `requestSubAgent` or
     * `requestInteraction` calls. Empty means no override is active.
     */
    get inputOverrides(): import("../state/ai-assistant-slice").InputOverride[];
    /**
     * Resolves the effective chat-input behaviour while the assistant is
     * executing. Per-call overrides take precedence over the agent-level
     * config; among overrides the most restrictive wins (`'hidden'` >
     * `'disabled'`). Falls back to the active agent's config, then `'disabled'`.
     */
    get effectiveChatInputDuringExecution(): ChatInputDuringExecutionMode;
    /** Most recent prompt token count from the AI provider, if available. */
    get contextTokens(): number | undefined;
    set contextTokens(value: number | undefined);
    /** Context window size for the active model, if known. */
    get contextLimit(): number | undefined;
    set contextLimit(value: number | undefined);
    /**
     * Cost plus the four token buckets for this session — derived from the transcript
     * on every change, so it includes sub-agent turns and spend banked by a compaction.
     */
    get sessionUsage(): AggregateUsage;
    set sessionUsage(value: AggregateUsage);
    /**
     * Aggregated USD cost across every chat turn in this session.
     *
     * Read-only (unlike previously): it is a projection of `sessionUsage`, and a
     * host assigning it would have set a figure the next transcript change overwrites.
     */
    get sessionCostUsd(): number;
    /**
     * Distinct models the registry currently resolves to — the live session's model chips.
     *
     * `providerStatuses` carries one entry per registry SLOT, so a vendor configured across several
     * tiers that happen to resolve to the same model reports it once per tier, and the chips
     * rendered it once per tier too. Which tier a model came from is not what these chips are for.
     *
     * Deduped on the label actually displayed, not on `status.model`: a slot whose provider reports
     * no model falls back to the slot's own name, and those are distinct per slot, so keying on the
     * label keeps them from collapsing into one another.
     */
    get uniqueProviderModels(): CostSessionModelEntry[];
    /**
     * Build history for display: most recently worked on first.
     *
     * The stored order is insertion order (see `upsertRecord`), which leaves a project returned to
     * sitting wherever it was first seen — so ordering is applied here, on read, where it also
     * covers rows a host provider supplied.
     */
    get sortedCostSessionHistory(): CostSessionRecord[];
    /**
     * Lifetime usage across every project in the history scope — cost and all four token
     * buckets. What the Usage tab's lifetime section renders.
     *
     * Rows ONLY — deliberately not rows + live session. Each row holds a project's lifetime
     * usage and the active project's row is kept current on every transcript change, so
     * adding the live total would count this project's spend twice. That double-count is what
     * made the figure grow on every refresh: a page-hide banked a row while the restored
     * session still carried the same spend.
     */
    get cumulativeUsage(): AggregateUsage;
    /** Lifetime USD across every project in the history scope. */
    get cumulativeCostUsd(): number;
    /**
     * Whether the live session figures amount to this project's whole history, which is true
     * exactly when the transcript is restored on load. Drives the Usage tab's live labels
     * ("Project cost" vs "Session cost") so they state the scope rather than imply one.
     *
     * Note this is NOT what makes the *rows* lifetime — those bank their pre-transcript spend
     * (see `CostSessionRecord.banked`) and are lifetime either way.
     */
    get liveUsageIsProjectLifetime(): boolean;
    /** Resolved cost tab and build-history settings from `chatConfig.costHistory`. */
    get resolvedCostHistory(): ResolvedCostHistoryConfig;
    /**
     * Total tokens across every chat turn in this session — all four buckets added up.
     * Read-only; the per-bucket split is on `sessionUsage`.
     */
    get sessionTokensConsumed(): number;
    /** Label shown on the Usage tab for the in-flight session (app title or first user phrase). */
    get liveCostSessionTitle(): string | undefined;
    /**
     * Write this session's usage into persisted build history (`localStorage`) as a single
     * row keyed by the session key, replacing any existing row for that key.
     *
     * **Idempotent** — safe to call per turn, on tab close, and on a title change alike.
     * The name is historical: it does NOT end the session or reset the running totals.
     * Resetting them is what used to double-count spend, since the row was banked while the
     * persisted transcript still carried the same usage, so a reload restored it and the
     * Usage tab counted both.
     *
     * No-op unless `chatConfig.costHistory.enabled` is `true`, when the live session has no
     * usage yet, or when the session has no key to upsert against. Gating on the *live*
     * figures (not the banked total) means a reload that restores nothing rewrites nothing.
     */
    finalizeCostSession(options?: {
        title?: string;
    }): void;
    /**
     * Spend for `projectKey` that predates the current transcript, resolved once per SESSION and
     * shared by every element instance wired to it (see `banked-usage-baselines`), so it cannot
     * drift as the row it came from is rewritten by our own upserts.
     *
     * The two cases turn on whether the transcript is authoritative for this project:
     *
     * - **Transcript restored** (`persistenceActive`): it already accounts for everything after
     *   the previous row's own banked figure, so carry that figure forward unchanged. Re-banking
     *   `usage` here would add the restored history to itself on every reload.
     * - **Transcript starts empty**: nothing on screen accounts for the previous total, so the
     *   whole of it becomes banked. Without this the next turn would rewrite the row downwards
     *   to just that turn, losing the project's earlier spend.
     *
     * The baseline is deliberately NOT element state. This element is remounted repeatedly inside
     * a single session (the host re-rendering its layout, popping out, docking) and a fresh
     * instance re-deriving would take the second branch against rows that already hold the live
     * transcript — banking the session as its own baseline. Keying on the session instead means a
     * remount inherits the baseline that transcript was paired with.
     */
    private bankedUsageFor;
    /**
     * Re-baseline this session against an emptied transcript: what came before, plus what the
     * transcript being discarded proved. `discarded` must be read BEFORE the live figures are
     * reset, since that is the only record of the latter.
     *
     * Not the same as dropping the baseline. Dropping it re-derives through `persistenceActive`,
     * and with persistence ON that carries the row's *old* banked figure forward — discarding
     * every turn between the last baseline and the clear. The transcript is gone either way, so
     * the branch that reads it as authoritative no longer applies.
     *
     * Derived from the memoised baseline and the live figure rather than from the project's row,
     * because a row is only as current as the last `finalizeCostSession` — which skips its write
     * while the ledger is unloaded, and while the session has no usage yet.
     */
    private rebaselineOnClearedTranscript;
    showConfirmationDialog(confirmationMessage: string, modalParent: HTMLElement, onConfirm: () => void, onCancel?: () => void): void;
    /** Remove one entry from persisted build history. */
    removeCostSessionFromHistory(id: string): void;
    /** Clear all persisted build history for this assistant scope. */
    clearCostSessionHistory(): void;
    private getCostHistoryScope;
    private reloadCostSessionHistory;
    /**
     * The persistence provider, but only when it implements BOTH cost-history methods.
     *
     * Both or neither: a provider that can save but not load would silently accumulate a
     * ledger nobody ever reads back, so a partial implementation is treated as absent and
     * falls back to `localStorage`.
     */
    private costHistoryProvider;
    /**
     * Adopt `records` as the live list, and schedule a write to whichever backend owns cost
     * history — the provider when the host implements it, else `localStorage`.
     *
     * The list is adopted synchronously because that is what the Usage tab renders; only the
     * *write* is deferred. This runs on every usage change (about once per LLM call, plus once
     * per sub-agent turn) and each write serialises the whole ledger, so coalescing cuts a
     * per-turn `localStorage.setItem` or network round-trip down to one per quiet period. Safe
     * because the write is a full replace of idempotent state: the newest call wins and no
     * intermediate state is meaningful. Flushed on `pagehide` and disconnect so nothing is lost.
     */
    private persistCostHistory;
    /**
     * Write the pending ledger now. Safe to call when nothing is pending.
     *
     * Provider writes are fire-and-forget and best-effort: the ledger is accounting, not
     * conversation content, so a failed write is logged and never surfaced into the chat. On
     * `pagehide` the `localStorage` path completes synchronously; a provider write is subject to
     * the same unload race as every other network save the assistant makes.
     */
    private flushCostHistory;
    /**
     * Finalize the previous session when the host-provided title changes — a rename should land on
     * the row under its old name rather than silently relabelling accrued spend.
     *
     * Only when the session key still matches the one that title accrued under. Hosts change
     * `costHistory.title` on project SWITCH too, and if the key has already flipped, this would
     * upsert the outgoing project's live usage onto the incoming project's row as
     * `banked + old live`, inflating it. Under the old append model a mis-keyed write left a stray
     * row; with the upsert it overwrites a real one.
     */
    private syncActiveCostSessionTitle;
    /** Active model id resolved from the AIProvider, if exposed. */
    get activeModel(): string | undefined;
    set activeModel(value: string | undefined);
    /** Name of the AI provider used on the most recent turn. */
    get activeProviderName(): string | undefined;
    set activeProviderName(value: string | undefined);
    /** Per-provider status snapshot used by the settings panel. */
    get providerStatuses(): AIProviderRegistryStatusEntry[];
    set providerStatuses(value: AIProviderRegistryStatusEntry[]);
    private _suggestionsGeneration;
    get inputValue(): string;
    set inputValue(value: string);
    attachments: ChatAttachment[];
    attachmentErrors: string[];
    /** Whether the loading spinner is currently visible. Controlled by the loading delay timer. */
    showLoadingIndicator: boolean;
    /** Bump to re-render app settings toggles when the host provider notifies a change. */
    appSettingsRevision: number;
    /** Whether the settings modal is open. */
    settingsOpen: boolean;
    /**
     * True while `downloadDebugLog` is building the log (GENC-1351 §5.8). Drives the
     * download button's disabled + spinner state — the lifetime-stitch path is async
     * (a provider `loadDiagnostics` round-trip), so without feedback a click looks
     * like nothing happened. Per-instance and transient (not shared/persisted): each
     * element has its own settings modal.
     */
    downloadingDebugLog: boolean;
    /** Active tab inside the settings modal. */
    settingsModalTab: SettingsModalTab;
    /** Bound to the design-system modal via the template ref. */
    settingsModal?: Modal;
    /**
     * GENC-1511 — persistence has failed repeatedly and it is no longer saving. Drives a BLOCKING
     * modal with a single Reload action: the whole failure class this ticket exists for is one the
     * user cannot see, and a passive indicator is close to invisible (Will worked for 3h17m past
     * the point his session stopped saving, with nothing on screen to tell him).
     *
     * Set only at a moment when reloading is SAFE — the persister gates it on the active agent
     * being resumable, so we never advise a reload while an agent's in-flight work would be lost.
     */
    persistenceBroken: boolean;
    /**
     * Showing and hiding the modal hangs off the PROPERTY, so `persistenceBroken` is the single
     * entry point — for this element's own persister callback and for a host reporting a failure in
     * a keyspace this element knows nothing about (Create's project payload, GENC-1511 §17).
     *
     * It has to be here rather than at the call site because of the `cancel` listener: `cancel`
     * does not bubble, so it cannot be bound from the template, and a caller that shows the modal
     * without attaching it gets a modal Escape closes — which defeats the whole point. Routing
     * every path through one place makes a dismissible variant unreachable.
     */
    persistenceBrokenChanged(_old: boolean, broken: boolean): void;
    /**
     * Body copy for the modal; `undefined` uses the session-oriented default in the template. A
     * host reporting a different keyspace overrides it — "this session has stopped saving" is
     * simply untrue of a failed project write.
     */
    persistenceBrokenMessage?: string;
    /**
     * Optional second line, already composed. A STRING rather than the message-count fields it
     * replaces: quantifying is caller-specific ("N messages", "your last N changes"), and keeping
     * the template ignorant of what is being counted is what makes it reusable.
     */
    persistenceBrokenDetail?: string;
    /** The modal itself, so it can be shown and made non-dismissible. */
    persistenceModal?: Modal;
    /** Bound to the settings modal tab bar — used to resync selection after reopen. */
    settingsTabsEl?: SettingsModalTabsElement;
    /** Light-DOM nodes assigned to settings modal slots (via `slotted()` bindings). */
    settingsModelSlotted: HTMLElement[];
    settingsAppSlotted: HTMLElement[];
    settingsCostCurrentSlotted: HTMLElement[];
    settingsCostHistorySlotted: HTMLElement[];
    /** Persisted finalized cost sessions shown on the Usage tab build history list. */
    costSessionHistory: CostSessionRecord[];
    /** Whether the splash overlay is currently showing (no messages and showSplash is enabled). Reflected as a boolean attribute on the host. */
    showingSplash: boolean;
    private driver?;
    private driverCleanup?;
    private loadingTimer;
    private unsubBus?;
    /** Unsubscribe handle for the `tool-loop-end` budget latch (GENC-1464). */
    private _unsubBudgetLatch?;
    /** Unsubscribe handle for the provider-registry change listener (observable registries only). */
    private unsubProviderRegistry?;
    /** Unsubscribe handle for {@link (AssistantAppSettingsProvider:interface).subscribe}. */
    private unsubAppSettingsProvider?;
    /** Title tracked for the in-flight cost session (used when `header-title` changes). */
    private _activeCostSessionTitle;
    /**
     * Session key `_activeCostSessionTitle` was captured under, so a title change can tell a rename
     * (same key) from a project switch (key already flipped) — see `syncActiveCostSessionTitle`.
     */
    private _activeCostSessionKey?;
    /**
     * Whether the row list reflects storage yet. Gates cost writes so a project's banked
     * spend is never derived from a ledger that simply hasn't arrived.
     */
    private _costHistoryLoaded;
    /**
     * Set when a provider read failed. Latches writes off for this page-load: `saveCostHistory`
     * replaces the whole stored list, so writing anything derived from the empty list we fell back
     * to would destroy rows we never managed to read.
     */
    private _ledgerUnreadable;
    /** Pending (adopted but unwritten) ledger changes — see `persistCostHistory`. */
    private _ledgerDirty;
    private _ledgerFlushTimer?;
    private _handlePageHide;
    private haloStartPublished;
    /** Fingerprint of the agents array used to build the current driver. Used by agentsChanged to skip spurious rebuilds. */
    private _driverAgentsKey?;
    /** Bound to the messages container via the template ref — assigned by FAST before connectedCallback logic runs. */
    messagesEl?: HTMLElement;
    /** Bound to the rapid-text-area chat input via the template ref. */
    chatInputEl?: HTMLElement;
    /**
     * Explicit composer height (px) once the user has dragged the resize handle;
     * `null` keeps the default auto-grow behaviour. Written into the
     * `--composer-height` custom property via the `composerHeightStyle` getter.
     */
    composerHeightPx: number | null;
    /** Pointer Y and composer height captured at the start of a resize drag. */
    private _resizeStartY;
    private _resizeStartHeight;
    /** The composer's natural (un-resized) height, measured on first drag — used
     * as the lower bound so the box can't be dragged smaller than it started. */
    private _composerNaturalHeight;
    /** Upper bound for the current drag — capped to the headroom above so growing
     * the composer never pushes the input row's buttons off the bottom. */
    private _resizeMaxHeight;
    /**
     * True when the user's last interaction (pointer or keyboard focus) was inside
     * the assistant. Defaults to true so the assistant claims focus on first mount.
     * Auto-focus features (chat input on idle, widget inputs on mount) gate on this
     * to avoid stealing focus from elsewhere on the page.
     */
    isEngaged: boolean;
    private _handleGlobalInteraction;
    /** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
    private _userScrolledAway;
    private _scrollListener?;
    private static readonly SCROLL_BOTTOM_THRESHOLD_PX;
    /** Context-usage percentage that fires the one-shot `context.threshold-crossed` event. */
    private static readonly CONTEXT_USAGE_WARN_PERCENT;
    /** Last `contextTokens` logged to the timeline — dedupes the `context.updated` event. */
    private _lastLoggedContextTokens?;
    /** Whether the one-shot ≥80% `context.threshold-crossed` event has fired this session. */
    private _contextThresholdCrossed;
    /**
     * Interaction widgets that grow mid-lifecycle (e.g. expanding a "More info"
     * panel, appending an SSE status row, revealing generated code) bubble a
     * `request-scroll-to-bottom` event up from `ai-chat-interaction-wrapper`,
     * which observes them with a ResizeObserver. We re-snap to the bottom on
     * receipt, honouring `_userScrolledAway` so a user reading old messages is
     * not yanked back when content elsewhere grows.
     */
    private readonly _handleRequestScrollToBottom;
    /**
     * Unsubscribes a one-shot state subscription created when this element connects
     * mid-execution. The subscription watches for `state` becoming 'idle' so that
     * this element can stop its loading timer and sync the halo even when the
     * originating `send()` ran on a different element instance.
     */
    private _executionCompletionUnsub?;
    /**
     * True while UI preferences are being applied programmatically (config defaults
     * on connect, or a loaded-preferences restore) — suppresses the save hook so we
     * don't persist config defaults or re-persist what we just loaded. Cleared once
     * the apply completes; a genuine *user* toggle (menu switch) runs with it false
     * and saves (GENC-1351).
     */
    private _applyingPreferences;
    /**
     * Fresh-store flag (`= isNewStore`) captured in `connectedCallback`. Only a
     * freshly created store takes the config/stored-prefs baseline; an existing
     * store — a reload with a live in-realm store, or the popped-out second instance
     * — already holds the correct state, so `applyPreferenceBaseline` no-ops for it.
     */
    private _freshStore;
    /**
     * True once `connectedCallback` has fully wired the initial session (GENC-1351
     * §5.12). Guards `sessionKeyChanged` so the initial, pre-connect `session-key`
     * set doesn't trigger a re-key — only a genuine change to a connected element
     * switches the session. Reset on disconnect.
     */
    private _sessionActive;
    /**
     * The key of the session currently WIRED to this element (its driver/store/
     * `_sessionRef`). Authoritative for `getStateKey()` once a session is active,
     * because the `session-key` attr can run ahead of an actual switch: a mid-turn
     * rebind is deferred to idle (§5.12), and reading the attr in that window would
     * retarget autosave/diagnostics at a not-yet-activated key and silently drop the
     * rest of the live turn. Set by `activateSession`. Undefined before first connect.
     */
    private _activeStateKey?;
    /**
     * Target key of a `session-key` rebind that arrived mid-turn and was deferred
     * (§5.12). Applied by `applyDeferredSessionSwitch` once the driver goes idle. The
     * latest rebind wins if several land while busy. Undefined = no switch pending.
     */
    private _deferredSwitchToKey?;
    showHalo: 'no' | 'orchestrating' | 'agent';
    private syncShowHalo;
    /** True when there is a pending (unresolved) interaction. */
    get hasActivePendingInteraction(): boolean;
    showHaloChanged(): void;
    private getActiveToolNames;
    /**
     * Messages rendered in the scrolling list, filtered by the current toggle
     * state. Tool-related messages (those with toolCalls or toolResult) are hidden
     * when `showToolCalls` is false.
     *
     * Excludes the trailing interaction (see `activeInteractionRow`), which is
     * rendered in a separate pinned slot so toggling the message filters never
     * re-creates a live interaction widget.
     *
     * Marked `@volatile` because the filter branches conditionally access
     * `showToolCalls`, `showThinkingSteps`, and `showAgentSwitchIndicator`
     * depending on message content. Without it, FAST would only track the
     * observables accessed on the last evaluation and miss toggle changes that
     * happen to hit an untracked branch.
     */
    get visibleMessages(): ChatMessage[];
    /**
     * The trailing interaction, rendered in a pinned slot below the scrolling
     * list rather than inside it. Reads only `messages` (never the toggle state),
     * so a cog-menu toggle leaves this binding — and the live widget's DOM —
     * untouched. See `trailingInteractionRow` for the rationale.
     */
    get activeInteractionRow(): ChatMessage[];
    agentsChanged(): void;
    /** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
    private getAgentsKey;
    /**
     * Pure factory — creates a ChatDriver or OrchestratingDriver based on the
     * current agent configuration. Does not wire event listeners or register in
     * the driver registry.
     */
    /**
     * Warn at config time if any stateful agent (one with lifecycle hooks) is
     * configured in a way that makes it unreachable: `excludeFromClassifier`
     * removes the classifier path, so the only entry left is manual pinning —
     * which requires both `manualSelection.enabled` on the agent and the picker
     * itself being enabled at the assistant level.
     */
    private warnUnreachableStatefulAgents;
    private createDriver;
    /**
     * Attaches event listeners to the current driver. Stores a cleanup function
     * so `unwireDriver` can remove them precisely. Safe to call multiple
     * times — unwires any existing listeners first.
     */
    private wireDriver;
    /**
     * Removes event listeners attached by `wireDriver`. Does not destroy
     * the driver or remove it from the registry.
     */
    private unwireDriver;
    /**
     * Tear down the session's currently-wired driver (GENC-1351): unwire this view, drop
     * the driver from the registry, and dispose it — which fires the active agent's
     * `onDeactivate` → `machine.stop()`. Shared by `agentsChanged`, `clearSession`, and
     * `switchSession` so the teardown stays identical. `unwireDriver` does not clear
     * `this.driver`, so the instance is still readable here for disposal.
     */
    private teardownDriver;
    /**
     * Build (or adopt from the registry) a fresh driver for `key` against the current
     * agents and wire it to this view (GENC-1351). Records the agents fingerprint so a
     * later `agentsChanged` skips a spurious rebuild. Shared by `agentsChanged` and
     * `clearSession`.
     */
    private rebuildDriver;
    /**
     * Wire up the session identified by `key` on this element: preference baseline,
     * driver (build/reuse) + wiring, restore (fresh store only), and the per-session
     * view state (splash, loading-resume, suggestions, context/provider status, cost
     * history). Extracted from `connectedCallback` (GENC-1351 §5.12) so an in-place
     * session switch (`switchSession`) can re-run exactly this against a new
     * key without re-adding element-lifetime listeners. Session-scoped only — the
     * element-lifetime wiring (DOM/document/window listeners, provider-registry
     * subscription, app-settings, pagehide) stays in `connectedCallback`.
     */
    private activateSession;
    connectedCallback(): void;
    /**
     * Switch this element to a different session **in place**, without a page reload
     * (GENC-1351 §5.12) — the load-a-different-project flow. Tears down the current
     * session's driver + per-key registries and restores the target key's session
     * (its transcript and any mid-flow stateful agent). Fired automatically by
     * `sessionKeyChanged` when a host rebinds
     * `session-key` on a connected element; may also be called directly.
     *
     * Clobber-safe: the outgoing session's pending autosave is cancelled BEFORE the
     * store swaps, so its transcript can never be written to the incoming session's
     * file. Idle-only — a switch while a turn is running is refused.
     *
     * @param oldKey - the state key being switched away from.
     * @param newKey - the state key to activate.
     * @public
     */
    switchSession(oldKey: string, newKey: string): void;
    /**
     * FAST reflection hook for the `session-key` attr: a change on a **connected**
     * element re-keys the session in place (GENC-1351 §5.12). Guarded by
     * `_sessionActive` so the initial, pre-connect set does not switch — the first
     * key is wired by `connectedCallback`.
     */
    sessionKeyChanged(_oldValue: string | undefined, _newValue: string | undefined): void;
    disconnectedCallback(): void;
    private resolveContextLimit;
    private loadProviderStatuses;
    chatConfigChanged(): void;
    /**
     * `persistence` (like `chatConfig`) can bind after `connectedCallback` — a host may
     * resolve the `provider` asynchronously (an auth token, a feature flag). The connect
     * path gates the whole persistence init on a provider being present *at connect*, and
     * nothing else re-triggers it — so a late provider would silently never restore the
     * session or load stored preferences (GENC-1351 #13). Mirror `chatConfigChanged`: when
     * a provider first appears on a connected element whose store is still fresh, run the
     * fresh-store init now. Guards keep it a one-shot:
     *  - pre-connect sets are handled by `connectedCallback` (`_sessionActive` is false);
     *  - a provider that was already present (incl. the internal `enabled`-only
     *    reassignments) is a no-op (`oldValue?.provider`);
     *  - a non-fresh (restored) store already holds its session (`_freshStore`).
     */
    persistenceChanged(oldValue: SessionPersistenceConfig | undefined, newValue: SessionPersistenceConfig | undefined): void;
    showingSplashChanged(): void;
    private syncShowingSplash;
    /**
     * Runs side effects that were previously in `messagesChanged()`.
     * Called from the `messages` setter after dispatching to the store.
     */
    private handleMessagesUpdate;
    showLoadingIndicatorChanged(): void;
    private scrollToBottom;
    private static readonly DEFAULT_LOADING_DELAY_S;
    private static readonly DEFAULT_SUGGESTION_COUNT;
    private static readonly MS_PER_SECOND;
    private startLoadingTimer;
    private clearLoadingTimer;
    private stopLoadingTimer;
    /** Called when the user clicks the popout button. */
    handlePopout(): void;
    /** Returns a cache key for this instance, or undefined if the component has no identity. */
    private getStateKey;
    /**
     * The session key implied by the element's current attributes (GENC-1351 §5.12).
     * An explicit `session-key` IS the complete session identity — the host fully
     * qualifies it (e.g. a per-project id), so `headerTitle` is NOT appended
     * (appending it was multi-window disambiguation, obsolete under the single-window
     * orchestration/pinning model for keyed hosts). Without a `session-key`, fall back
     * to `id` + `headerTitle` (the original scheme, kept so existing consumers — e.g. a
     * title-only key with no `id` — aren't re-keyed). This is the *desired* key;
     * `getStateKey` returns the *wired* key, which can lag during a deferred switch.
     */
    private computeStateKeyFromAttrs;
    /**
     * Record a meta/lifecycle event onto this session's debug-log timeline
     * (exported via {@link FoundationAiAssistant.getDebugLog}). Every event
     * auto-carries `placement` so a session emitted from both the bubble and the
     * layout panel stays disambiguated. Keyed by `stateKey` so the timeline is
     * shared across pop-in/pop-out and survives driver rebuilds.
     */
    private logMeta;
    private readonly _agentPickerToggle;
    toggleAgentPicker(): void;
    /** Two-step confirm for the destructive Clear action; reset whenever the menu toggles. */
    confirmingClear: boolean;
    confirmingCompact: boolean;
    private readonly _sessionMenuToggle;
    /**
     * Whether the lifecycle-menu button shows. It occupies the stop-button slot,
     * so it only appears when idle (mutually exclusive with Stop). Gated on
     * persistence — the menu's actions (Compact, Clear, the "remember chat" toggle)
     * only make sense with a provider configured, so it's hidden entirely without
     * one. A host can additionally force it off via `chatConfig.ui.showSessionMenu:
     * false` even when a provider is present (e.g. a guided onboarding flow where
     * clearing/compacting mid-journey would be disruptive).
     */
    get sessionMenuEnabled(): boolean;
    /** The persistence toggle row renders only when a provider is configured. */
    get persistenceToggleable(): boolean;
    /** Whether persistence is currently active (on + a provider) for this session. */
    get persistenceActive(): boolean;
    /**
     * Whether the menu's "Compact" action would do anything (GENC-1351 §5.7).
     * Delegates to `driver.canCompact()` so the gate uses the exact same history
     * `compact()` acts on — one source of truth. Also reads `messages`, purely to
     * stay reactive as the conversation grows: the driver's history isn't a tracked
     * observable, so the button keys its re-evaluation off the Redux mirror.
     */
    get compactable(): boolean;
    toggleSessionMenu(): void;
    /**
     * Set persistence on/off for this session (the menu switch). Reassigns the
     * `persistence` object so the `@observable` fires. Turning it **off** drops the
     * stored snapshot; **on** captures the current chat immediately. Either way the
     * *choice* is remembered in the preferences blob (`persistPreferences`) — stored
     * independently of the snapshot, so it survives the clear and re-applies on the
     * next load (GENC-1351).
     */
    setPersistenceEnabled(enabled: boolean): void;
    /** Toggle persistence (convenience / programmatic). */
    togglePersistence(): void;
    /** Snapshot the user-changeable settings into a persistable preferences blob. */
    private gatherPreferences;
    /**
     * Persist the current UI preferences whenever the user changes a setting
     * (GENC-1351). Fires **regardless of "remember chat"** — display choices are the
     * user's, not tied to whether the chat is remembered — needing only a provider
     * that implements `savePreferences`. No-op while preferences are being applied
     * programmatically (`_applyingPreferences`), so config defaults and loaded prefs
     * aren't re-saved.
     */
    private persistPreferences;
    /**
     * Apply the effective UI-preference baseline onto the live toggles for a fresh
     * store (GENC-1351 #2). Precedence, lowest → highest:
     *
     *   slice defaults  <  `chatConfig.ui` defaults  <  stored user preferences
     *
     * Recomputed from scratch (not layered) on every call, because its two inputs
     * arrive at unpredictable times: `chatConfig` is a parent-supplied `@observable`
     * that may bind *after* `connectedCallback`, and stored prefs load asynchronously.
     * So it re-runs from `connectedCallback`, `chatConfigChanged`, and once
     * `loadPreferences` resolves — whichever order they happen in, the final state is
     * the same. No-op once the store isn't fresh (an existing/popped-out store owns
     * its state) or the user has toggled something (their choice then wins, and
     * `chatConfig` doesn't change post-startup in practice). Save hook suppressed so
     * applying the baseline never persists as if it were a user choice.
     *
     * This is the fix for "nuking server prefs drops to all-disabled instead of the
     * `chatConfig` defaults": stored prefs are simply absent, so the baseline falls
     * through to the config layer — which the old apply-once-on-connect path missed
     * whenever `chatConfig` hadn't bound yet.
     */
    private applyPreferenceBaseline;
    /**
     * Compact the conversation (GENC-1351 §5.7) — the menu's "Compact" action.
     * Delegates to the driver, which summarizes older turns into a single
     * `compacted-summary` message (destructive, atomic). On success records a
     * high-importance `context.compacted` meta-event capturing the summary; the
     * normal `history-updated` → autosave path then persists the smaller
     * transcript. No-op when there is nothing to compact or the provider cannot
     * summarize (the driver returns `null`).
     */
    compactSession(): Promise<void>;
    /**
     * Clear the session — the "New chat" action (GENC-1351 §5.6). Tears down the
     * active driver (firing the stateful agent's `onDeactivate` → `machine.stop()`),
     * resets the store, rebuilds a fresh driver with no history, and wipes durable
     * storage. Mirrors `agentsChanged`'s teardown, minus the history reseed.
     *
     * Emits `session-cleared` (`SessionClearedDetail`, declared on the class `@fires`)
     * with `sessionKey`, `agentName`, and `agentSnapshot` captured *before* teardown, so
     * the host can clean up any external state the active agent was working on (generated
     * files, server-side scratch, etc.). Bubbles and composed.
     */
    clearSession(): Promise<void>;
    /**
     * Programmatically pin an agent by name. Returns `true` if the pin was
     * applied, `false` if the agent isn't in the configured agents array or
     * the call was suppressed by `force: false`.
     *
     * With `force: false`, the call is a no-op when a `picker.defaultAgent` is
     * configured and the user has already moved away from it (either by picking
     * another agent or by switching to Auto). This lets hosts seed an opinion
     * without overriding an explicit user choice.
     *
     * @public
     */
    setAgent(agentName: string, options?: {
        force?: boolean;
    }): boolean;
    /** Whether the picker toggle button should appear. Mirrors the picker's own visibility rule. */
    get agentPickerEnabled(): boolean;
    /** Whether the settings modal AI Model section has slotted app content, context usage or budget figures. */
    get settingsModelSectionVisible(): boolean;
    /** Built-in context window indicator (generic platform concern). */
    get settingsContextUsageVisible(): boolean;
    /**
     * Built-in per-vendor budget meter (GENC-1464) — the money sibling of
     * {@link FoundationAiAssistant.settingsContextUsageVisible}, with the same
     * shape of gate: not configured off, and there is data to show. "Data" here
     * is "at least one vendor has figures", so a host that feeds nothing (or only
     * unlimited vendors) gets no meter and no empty container — exactly the
     * context indicator's absent case.
     *
     * Answered from the RAW slice state rather than from
     * {@link FoundationAiAssistant.settingsBudgetRows}, whose `.length` would
     * format every row (a `formatUsd` pair and a `budgetPercentOf` each) only to
     * ask whether any exist. This is read several times per render pass — the
     * getter, the section gate, the template's `when` — so it stays as cheap as
     * the context sibling's `!= null`.
     *
     * @internal
     */
    get settingsBudgetUsageVisible(): boolean;
    /**
     * The meter's rows — one per vendor that currently has figures, in
     * `BUDGETED_VENDORS` order (the proxy's own metered list) so the rendering is
     * stable however the host ordered its feed calls. Pre-formatted; see
     * {@link SettingsBudgetRow} for why the text is unclamped while the bar is.
     *
     * @internal
     */
    get settingsBudgetRows(): readonly SettingsBudgetRow[];
    /** Whether the settings modal UI Builder section should render. */
    get settingsAppSectionVisible(): boolean;
    /** App toggles currently visible per each entry's `visible()` predicate. */
    get visibleAppSettingsToggles(): AssistantAppSettingsToggle[];
    /**
     * Host-supplied heading for the toggle group, or `undefined` for none — the assistant has no
     * default. Suppressed when no toggle is currently visible, so a `visible()` predicate hiding
     * the last one takes its heading with it rather than leaving a label over nothing.
     */
    get appSettingsTogglesHeading(): AssistantAppSettingsHeading | undefined;
    getAppSettingValue(id: string): boolean;
    setAppSettingValue(id: string, value: boolean): void;
    private wireAppSettingsProvider;
    private unwireAppSettingsProvider;
    get settingsCostCurrentSectionVisible(): boolean;
    /**
     * Whether the build-history section has anything in it — the built-in row list, or a host's own
     * markup in the `settings-cost-history` slot.
     *
     * Slotted content is checked FIRST, ahead of `enabled`. That flag governs the *built-in* ledger:
     * it gates the row writes, the reload, and the lifetime sum — and it defaults to `false`
     * (`cost?.enabled === true`), while the Usage tab itself shows for `showCost`/`showTokens`, which
     * default true. So a host can legitimately render its own build history into the slot and never
     * opt into our `localStorage` ledger. Short-circuiting on `enabled` above the slot check hid that
     * host's markup outright, since the slot is the only thing in the container.
     */
    get settingsCostHistorySectionVisible(): boolean;
    /**
     * Whether the "Build history" heading has anything under it.
     *
     * Broader than {@link FoundationAiAssistant.settingsCostHistorySectionVisible} because the
     * heading also covers the lifetime total, which renders before the first row exists (rows are
     * written as spend accrues, the lifetime section only needs history switched on). Gating the
     * heading on the rows alone left that total sitting under nothing.
     */
    get settingsCostHistoryHeadingVisible(): boolean;
    /** Whether the built-in Usage tab summary block has anything to show. */
    get settingsCostSummaryVisible(): boolean;
    /**
     * Whether the lifetime-usage section has anything to say.
     *
     * Requires build history: with it off there are no rows, so `cumulativeUsage` falls back to
     * the live session and the section would restate the block directly below it under a
     * "lifetime" heading it hasn't earned.
     */
    get settingsCostLifetimeVisible(): boolean;
    /** Whether the Usage tab should appear in the settings modal. */
    get settingsCostTabVisible(): boolean;
    /** Whether session token usage should render on the Usage tab. */
    get settingsSessionTokensVisible(): boolean;
    /** Whether the chat header (and settings cog) should render. */
    get settingsCogVisible(): boolean;
    openSettingsModal(): void;
    closeSettingsModal(): void;
    onSettingsModalClosed(): void;
    setSettingsModalTab(tab: SettingsModalTab): void;
    /** Sync the active settings modal tab when the design-system tab bar changes. */
    onSettingsModalTabsChange(event: Event): void;
    /** Ensure the settings tab is selected when the modal opens. */
    private resetSettingsModalTabsOnOpen;
    /** Return to the settings tab when the modal closes on Cost. */
    private resetSettingsModalTabsOnClose;
    /**
     * Snap the tab underline to the settings tab without animating from Cost.
     * Used after hide/show when aria state is correct but the indicator drifted.
     */
    private snapSettingsModalTabIndicator;
    /** Hint text for the currently pinned agent, if any. Used in the toggle button tooltip. */
    get pinnedAgentHint(): string | undefined;
    /**
     * The pin is locked when a stateful flow is mid-stream — i.e. some agent's
     * `onActivate` has fired and it owns live state, and it hasn't yet called
     * `releaseAgent`. Derives directly from `flowOwnerAgentName`, the
     * single source of truth the orchestrator writes on activation and clears
     * on release. Until the user sends their first message, a freshly pinned
     * stateful agent is not yet active and the picker should remain free; the
     * user might change their mind and unpin without anything to clean up.
     */
    get pinLocked(): boolean;
    /** Tooltip shown on the picker toggle button. */
    get agentToggleTitle(): string;
    /**
     * Tint applied to the pin icon when an agent is pinned. Picked from the
     * brand palette by agent position (modulo the palette length), so each agent
     * gets a consistent colour across renders. `undefined` when nothing is
     * pinned, the agent isn't in the current array, or the host has opted out
     * via `chatConfig.picker.disablePinColours`.
     */
    get pinnedAgentColour(): string | undefined;
    /**
     * Placeholder shown in the chat input. Substitutes the pinned agent's name
     * when one is selected so the user has a clearer signal of where their
     * message will go; otherwise falls back to the host-provided placeholder.
     */
    get effectivePlaceholder(): string;
    toggleShowToolCalls(): void;
    toggleShowThinkingSteps(): void;
    toggleShowNarration(): void;
    toggleShowAgentSwitchIndicator(): void;
    setEnabledAnimations(animations: AiAssistantAnimation[]): void;
    /** The live current-page debug log (`{ readme, sessionUsage, timeline, meta }`). @public */
    getDebugLog(): DebugLog;
    /**
     * Build the current page's diagnostic entries (GENC-1351 §5.8) — the debug-log
     * timeline entries (`message`/`turn`/`event`) plus one `meta-snapshot` carrying
     * the export-time `meta` block. `getDebugLog()` reassembles these into the live
     * log; the persister's forward-capture path (`SessionPersister.flushDiagnostics`,
     * fed these via the `getDiagnosticEntries` dep) diffs them against what's already
     * been appended to the persisted stream.
     */
    private buildDiagnosticEntries;
    /**
     * The single `meta-snapshot` entry for right now — the export-time `meta` block
     * (agent summary, active prompt/state, context + cost) plus the `dedupSignature`
     * the forward-capture delta keys on.
     *
     * Separate from `buildDiagnosticEntries` because the download path needs a
     * FRESH one on its own: the persisted stream only re-appends this block when the
     * near-static config changes, so the newest stored snapshot's volatile half — the
     * `context` figures especially — is typically frozen at the session's first flush.
     */
    private buildMetaSnapshot;
    downloadDebugLog(): Promise<void>;
    /**
     * The log to download (GENC-1351 §5.8). When the provider persists diagnostics,
     * stitch the **whole lifetime** stream (every page load) rather than just the
     * current page's in-memory view: flush the current delta first so it's included,
     * then reassemble everything `loadDiagnostics` returns. Independent of "remember
     * chat" — diagnostics are a forensic stream keyed on provider capability, not on
     * whether the *chat* is remembered (like preferences). Falls back to the live
     * current-page log when diagnostics isn't available or the fetch fails.
     *
     * The stored `meta-snapshot`s are replaced by a fresh one (`withFreshMetaSnapshot`)
     * before reassembly. The persisted stream only re-appends that block when the
     * near-static config signature changes (see `collectDiagnosticsDelta`), so on a session
     * whose config never changes the newest STORED snapshot is the first one — its
     * `context` half (session cost/usage, context tokens, live agent state) frozen seconds
     * into the session, which is how a lifetime log came out reporting near-zero spend
     * against a transcript full of priced messages.
     */
    private buildDownloadLog;
    /**
     * The shared per-session `SessionPersister` that owns restore-core
     * save/restore/freeze (GENC-1351 §5.10). Created once per `stateKey` and shared by
     * every element view (bubble + panel) via the registry, so there is a single save
     * timer + carry-forward cache — no per-view double-writes. The deps are getters so
     * the persister always reads the current driver/store/config and never holds a
     * stale reference. `undefined` before the session key is available.
     */
    private persister;
    /**
     * The `SessionPersister` dependency getters bound to THIS element + `key`.
     * The host-owned getters (`getPersistence`/`getAgentNames`/`getDiagnosticEntries`)
     * read the live element; `getDriver`/`getStore` resolve by key from the registries.
     * Rebound onto the shared persister on every activation (see `activateSession`) so
     * a remounted element becomes the live source, not the torn-down creator (GENC-1351 §5.10 / #7).
     */
    private buildPersisterDeps;
    /**
     * Persistence is broken and it is now safe to advise a reload (GENC-1511). Shows a modal with
     * no way out but Reload — deliberately: continuing to work produces output that will be lost,
     * so anything dismissible is an invitation to keep going. The dismissal routes are both closed
     * explicitly — `show-close-icon` is off in the template, and the native `<dialog>`'s Escape
     * handling is cancelled on `mainElement` below, since `cancel` does not bubble and cannot be
     * bound from the template.
     */
    /**
     * Tell the user their work is no longer being saved, and block until they reload (GENC-1511).
     *
     * Public because the assistant is not the only thing that persists. Its own persister covers
     * `sessions/<key>`; a host may own other keyspaces whose failure is just as costly — Create's
     * `projects/<id>.json` holds the entity model, handlers and VFS, i.e. the app itself — and had
     * no way to reach this modal (§17 of the GENC-1511 plan).
     *
     * Both copy fields are optional and default to the session wording in the template, so the
     * common case stays a bare call. Idempotent while already broken: the property change only
     * shows a modal that is not already open.
     *
     * @param overrides - `message` replaces the body; `detail` adds/replaces the second line.
     */
    reportPersistenceBroken(overrides?: {
        message?: string;
        detail?: string;
    }): void;
    /** Withdraw the warning — a save landed, or the host's own store recovered (GENC-1511). */
    reportPersistenceRecovered(): void;
    /** This element's own persister said the session snapshot is not landing (GENC-1511). */
    private onPersistenceBroken;
    /** A save landed after a broken episode — withdraw the warning (GENC-1511). */
    private onPersistenceRecovered;
    /**
     * The modal's only action. A reload re-reads the stored snapshot, which is plain data parsed
     * from the server, so it also clears the serialisation fault that is the most likely cause.
     */
    reloadForPersistence(): void;
    /**
     * First-load persistence init for a freshly created store (GENC-1351). Restores
     * the user's remembered persistence-toggle choice — kept independently of the
     * session snapshot so it survives a clear; the host-configured `enabled` is only
     * the default for a first-ever load — then auto-restores the session when
     * persistence is (now) on.
     */
    private initPersistenceForNewStore;
    triggerFileInput(): void;
    handleFileSelect(e: Event): void;
    removeAttachment(attachment: ChatAttachment): void;
    removeAttachmentError(message: string): void;
    private isAcceptedFile;
    private readFileAsText;
    private processFiles;
    private loadSelectedFiles;
    handleSendClick(): void;
    /**
     * User clicked Stop. Cancels the current turn via the driver and flips the
     * button to "Stopping…" until the turn winds down (a tool mid-execution
     * finishes first). Guarded so a second click is a no-op.
     */
    handleStopClick(): void;
    handleSuggestionClick(suggestion: string): void;
    /**
     * Programmatically submit a message. Equivalent to the user typing into the
     * built-in composer and pressing send, but callable from external code — most
     * commonly a custom input rendered inside `slot="splash"` when paired with
     * `chatConfig.ui.hideComposer`.
     *
     * The input fully describes the turn: `text` and `files` REPLACE any current
     * composer state (they are not appended to it). Files are validated against
     * `chatConfig.ui.acceptedFiles` using the same pipeline as the composer's
     * attach button.
     *
     * Atomic on validation: if any file fails validation, no attachments are
     * committed and no send is dispatched. Failure messages are returned in
     * `errors` AND appended to the `attachmentErrors` observable so the built-in
     * chip UI still surfaces them when visible.
     *
     * Resolves once the underlying send has been kicked off — it does NOT await
     * the assistant's response.
     *
     * @beta
     */
    submitMessage(input: {
        text?: string;
        files?: File[];
    }): Promise<SubmitMessageResult>;
    private fetchSuggestions;
    /**
     * User text of the message currently being sent, stashed for the lifetime of `send()`.
     * `flow-owner-changed` fires while the driver routes this turn — before the message is
     * appended to raw history — so the flow-activation-prompt capture reads this rather than
     * a stale raw-history lookup. Cleared once the send settles. (GENC-1351 §5.9)
     */
    private _pendingSendPrompt?;
    private send;
    onChatHeaderMouseDown(e: MouseEvent): void;
    handleInteractionCompleted(e: Event): void;
    /**
     * The live {@link InteractionContext} for a pending interaction (GENC-1390).
     * `AiChatInteractionWrapper` pierces this across the shadow boundary to build the
     * widget's pending `interaction` descriptor. Narrow public surface over the
     * private `driver`; returns `undefined` for a resolved/evicted interaction.
     * @internal
     */
    getInteractionContext(interactionId: string): InteractionContext | undefined;
    /** Whether the message input shows a drag handle to resize its height.
     * On by default; hosts opt out via `chatConfig.ui.resizableInput: false`. */
    get isComposerResizable(): boolean;
    /** Inline style for the chat input: writes the chosen height into the
     * `--composer-height` custom property (read by `.chat-input` + its control in
     * `main.styles.ts`). Empty until the user drags (or when resizing is disabled),
     * so the default applies. */
    get composerHeightStyle(): string;
    /** Begin a composer resize drag. Captures the pointer so move/up keep firing
     * on the handle even when the cursor leaves its 7px band. */
    onComposerResizeStart(e: PointerEvent): void;
    /** Resize while dragging. Dragging up (clientY decreases) grows the composer;
     * the result is clamped between the natural height (lower) and the available
     * headroom/max (upper). No-op unless a drag is active (the handle holds pointer
     * capture), so plain hover-moves are ignored. */
    onComposerResizeMove(e: PointerEvent): void;
    /** End a resize drag and release the pointer capture. */
    onComposerResizeEnd(e: PointerEvent): void;
}
export {};
//# sourceMappingURL=main.d.ts.map