{"version":3,"sources":["../src/markdown-parsers-entry.ts","../src/voice/audio-playback-manager.ts","../src/voice/runtype-speech-engine.ts","../src/voice/fallback-speech-engine.ts","../src/voice/runtype-tts-entry.ts","../src/session-reconnect.ts","../src/index.ts","../src/markdown-parsers-eager.ts","../src/markdown-parsers-loader.ts","../src/postprocessors.ts","../src/utils/sanitize.ts","../src/utils/streaming-table.ts","../src/webmcp-bridge.ts","../src/utils/artifact-display.ts","../src/utils/target.ts","../src/components/ask-user-question-bubble.ts","../src/utils/dom.ts","../src/suggest-replies-tool.ts","../src/ask-user-question-tool.ts","../src/utils/formatting.ts","../package.json","../src/version.ts","../src/client.ts","../src/utils/message-id.ts","../src/utils/content.ts","../src/voice/runtype-voice-provider.ts","../src/voice/browser-voice-provider.ts","../src/voice/voice-factory.ts","../src/voice/browser-speech-engine.ts","../src/voice/read-aloud-controller.ts","../src/utils/speech-text.ts","../src/voice/runtype-tts-loader.ts","../src/session.ts","../src/utils/icons.ts","../src/utils/attachment-manager.ts","../src/utils/deep-merge.ts","../src/defaults.ts","../src/utils/tokens.ts","../src/utils/theme.ts","../src/utils/artifact-file.ts","../src/utils/spinner.ts","../src/utils/tool-loading-animation.ts","../src/utils/artifact-loading-status.ts","../src/utils/artifact-status-label.ts","../src/utils/code-highlight.ts","../src/utils/constants.ts","../src/utils/dropdown.ts","../src/utils/buttons.ts","../src/utils/artifact-custom-actions.ts","../src/components/artifact-card.ts","../src/components/artifact-inline.ts","../src/components/registry.ts","../src/components/artifact-preview.ts","../src/utils/morph.ts","../src/utils/copy-selection.ts","../src/utils/composer-history.ts","../src/utils/message-fingerprint.ts","../src/utils/auto-follow.ts","../src/utils/stream-animation.ts","../src/utils/overlay-host-stacking.ts","../src/utils/scroll-lock.ts","../src/utils/dock.ts","../src/utils/positioning.ts","../src/components/header-parts.ts","../src/components/header-builder.ts","../src/components/header-layouts.ts","../src/components/composer-parts.ts","../src/components/composer-builder.ts","../src/components/pill-composer-builder.ts","../src/components/panel.ts","../src/components/launcher.ts","../src/components/widget-view.ts","../src/components/message-bubble.ts","../src/components/reasoning-bubble.ts","../src/components/tool-bubble.ts","../src/components/approval-bubble.ts","../src/plugin-kit.ts","../src/components/approval-actions.ts","../src/components/suggestions.ts","../src/utils/event-stream-buffer.ts","../src/utils/event-stream-store.ts","../src/utils/throughput-tracker.ts","../src/components/event-stream-view.ts","../src/utils/roving-tablist.ts","../src/components/artifact-pane.ts","../src/utils/artifact-gate.ts","../src/utils/artifact-resize.ts","../src/components/forms.ts","../src/plugins/registry.ts","../src/utils/events.ts","../src/utils/actions.ts","../src/utils/storage.ts","../src/utils/component-parser.ts","../src/utils/component-middleware.ts","../src/components/feedback.ts","../src/ui.ts","../src/runtime/host-layout.ts","../src/runtime/init.ts","../src/utils/dom-context.ts","../src/utils/plugins.ts","../src/presets.ts","../src/index-core.ts","../src/utils/code-generators.ts","../src/components/demo-carousel.ts"],"sourcesContent":["import { Marked } from \"marked\";\nimport DOMPurify from \"dompurify\";\n\nexport { Marked, DOMPurify };\n","import type { PcmStreamPlayer } from \"../types\";\n\n/**\n * AudioPlaybackManager\n *\n * Manages streaming playback of PCM audio chunks via the Web Audio API.\n * Receives raw PCM data (24 kHz, 16-bit signed little-endian, mono),\n * converts to Float32 AudioBuffers, and schedules gap-free sequential\n * playback using AudioBufferSourceNode.\n *\n * Works on all browsers including iOS Safari (no MediaSource dependency).\n *\n * This is the default {@link PcmStreamPlayer} for both the realtime voice\n * provider and the `provider: 'runtype'` \"Read aloud\" path. It runs entirely on\n * the main thread, so it stays in the main bundle (no AudioWorklet module). The\n * jitter-buffered AudioWorklet player (`createPcmStreamPlayer` from\n * `@runtypelabs/persona/voice-worklet-player`) is the higher-quality, opt-in\n * alternative — inject it via `voiceRecognition.provider.runtype.createPlaybackEngine`\n * or `textToSpeech.createPlaybackEngine` and it lands in the consumer's bundle.\n *\n * With the default `prebufferMs: 0` this behaves exactly like a bare scheduler\n * (the realtime path), so that path is unchanged. A non-zero `prebufferMs`\n * (the read-aloud path passes ~200) holds incoming audio until a waterline of\n * samples is buffered before starting, and re-enters buffering on underrun —\n * softening the schedule-clock snap that a hand-scheduled BufferSource otherwise\n * turns into a click on bursty HTTP-streamed audio. This is a pragmatic\n * approximation of the worklet's audio-thread silence, not parity.\n */\nexport class AudioPlaybackManager implements PcmStreamPlayer {\n  private ctx: AudioContext | null = null;\n  private nextStartTime = 0;\n  private activeSources: AudioBufferSourceNode[] = [];\n  private finishedCallbacks: (() => void)[] = [];\n  private startedCallbacks: (() => void)[] = [];\n  private playing = false;\n  private streamEnded = false;\n  private pendingCount = 0;\n  // Fires once per playback session when the first sample is actually scheduled\n  // (loading→playing). Cleared by flush(); a mid-reply underrun does not re-fire.\n  private started = false;\n  // Explicit user pause via pause(); kept separate from the AudioContext's\n  // autoplay-policy suspension so ensureContext() doesn't auto-resume over it.\n  private userPaused = false;\n\n  // Prebuffer gate: while `buffering`, incoming samples accumulate in\n  // `pendingBuffers` until they reach `waterlineSamples`, then release into the\n  // scheduler. With `waterlineSamples === 0` the gate is off (realtime default).\n  private buffering: boolean;\n  private pendingBuffers: Float32Array[] = [];\n  private pendingSamples = 0;\n\n  // PCM format constants\n  private readonly sampleRate: number;\n  private readonly waterlineSamples: number;\n\n  // Remainder byte from a previous chunk when the chunk had an odd byte count.\n  // Network chunks don't respect 2-byte sample boundaries, so we carry over\n  // the orphaned byte and prepend it to the next chunk.\n  private remainder: Uint8Array | null = null;\n\n  constructor(sampleRate = 24000, options: { prebufferMs?: number } = {}) {\n    this.sampleRate = sampleRate;\n    const prebufferMs = Math.max(0, options.prebufferMs ?? 0);\n    this.waterlineSamples = Math.round((sampleRate * prebufferMs) / 1000);\n    this.buffering = this.waterlineSamples > 0;\n  }\n\n  /**\n   * Ensure AudioContext is created and running.\n   * Must be called after a user gesture on iOS Safari.\n   */\n  private ensureContext(): AudioContext {\n    if (!this.ctx) {\n      const w = typeof window !== \"undefined\" ? (window as any) : undefined;\n      if (!w) throw new Error(\"AudioPlaybackManager requires a browser environment\");\n      const AudioCtx = w.AudioContext || w.webkitAudioContext;\n      this.ctx = new AudioCtx({ sampleRate: this.sampleRate }) as AudioContext;\n    }\n    const ctx = this.ctx!;\n    // Resume if suspended (autoplay policy) — but never override an explicit\n    // user pause(): more audio may still stream in while paused.\n    if (ctx.state === \"suspended\" && !this.userPaused) {\n      ctx.resume();\n    }\n    return ctx;\n  }\n\n  /**\n   * Enqueue a PCM chunk for playback.\n   * @param pcmData Raw PCM bytes (16-bit signed LE mono)\n   */\n  enqueue(pcmData: Uint8Array): void {\n    if (pcmData.length === 0) return;\n\n    // Prepend any remainder byte from the previous chunk\n    let data = pcmData;\n    if (this.remainder) {\n      const merged = new Uint8Array(this.remainder.length + pcmData.length);\n      merged.set(this.remainder);\n      merged.set(pcmData, this.remainder.length);\n      data = merged;\n      this.remainder = null;\n    }\n\n    // If odd byte count, save the trailing byte for next chunk\n    if (data.length % 2 !== 0) {\n      this.remainder = new Uint8Array([data[data.length - 1]]);\n      data = data.subarray(0, data.length - 1);\n    }\n\n    if (data.length === 0) return;\n\n    const float32 = this.pcmToFloat32(data);\n    if (float32.length === 0) return;\n\n    if (this.buffering) {\n      // Hold until the prebuffer waterline fills, then release as a batch.\n      this.pendingBuffers.push(float32);\n      this.pendingSamples += float32.length;\n      if (this.pendingSamples >= this.waterlineSamples) this.releaseBuffer();\n    } else {\n      this.scheduleSamples(float32);\n    }\n  }\n\n  /**\n   * Signal that no more chunks will arrive.\n   * The onFinished callback fires after all queued audio has played.\n   */\n  markStreamEnd(): void {\n    // A reply shorter than the prebuffer never reaches the waterline; release\n    // whatever we held so it still plays.\n    if (this.pendingBuffers.length > 0) this.releaseBuffer();\n    this.streamEnded = true;\n    this.checkFinished();\n  }\n\n  /**\n   * Immediately stop all playback and discard queued audio.\n   */\n  flush(): void {\n    for (const source of this.activeSources) {\n      try {\n        source.stop();\n        source.disconnect();\n      } catch {\n        // Ignore errors from already-stopped sources\n      }\n    }\n    this.activeSources = [];\n    this.pendingCount = 0;\n    this.nextStartTime = 0;\n    this.playing = false;\n    this.streamEnded = false;\n    this.finishedCallbacks = [];\n    this.startedCallbacks = [];\n    this.remainder = null;\n    // Reset the prebuffer gate and the started latch for the next reply.\n    this.pendingBuffers = [];\n    this.pendingSamples = 0;\n    this.buffering = this.waterlineSamples > 0;\n    this.started = false;\n  }\n\n  /**\n   * Whether audio is currently playing or queued.\n   */\n  isPlaying(): boolean {\n    return this.playing;\n  }\n\n  /**\n   * Register a callback for when all queued audio finishes playing.\n   */\n  onFinished(callback: () => void): void {\n    this.finishedCallbacks.push(callback);\n  }\n\n  /**\n   * Register a callback fired once when audible playback first begins (the first\n   * sample is scheduled, after any prebuffer). Cleared by {@link flush}; a\n   * mid-reply underrun re-buffer does not re-fire it.\n   */\n  onStarted(callback: () => void): void {\n    this.startedCallbacks.push(callback);\n  }\n\n  /**\n   * Pause playback. Suspends the AudioContext clock; queued/scheduled audio\n   * freezes in place and {@link resume} continues exactly where it left off.\n   */\n  pause(): void {\n    this.userPaused = true;\n    if (this.ctx && this.ctx.state === \"running\") void this.ctx.suspend();\n  }\n\n  /** Resume playback after {@link pause}. */\n  resume(): void {\n    this.userPaused = false;\n    if (this.ctx && this.ctx.state === \"suspended\") void this.ctx.resume();\n  }\n\n  /**\n   * Clean up AudioContext resources.\n   */\n  async destroy(): Promise<void> {\n    this.flush();\n    if (this.ctx) {\n      await this.ctx.close();\n      this.ctx = null;\n    }\n  }\n\n  /** Release held prebuffer samples into the scheduler in arrival order. */\n  private releaseBuffer(): void {\n    this.buffering = false;\n    const held = this.pendingBuffers;\n    this.pendingBuffers = [];\n    this.pendingSamples = 0;\n    for (const samples of held) this.scheduleSamples(samples);\n  }\n\n  /** Schedule one Float32 sample block for gap-free playback. */\n  private scheduleSamples(float32: Float32Array): void {\n    if (float32.length === 0) return;\n    const ctx = this.ensureContext();\n\n    const buffer = ctx.createBuffer(1, float32.length, this.sampleRate);\n    buffer.getChannelData(0).set(float32);\n\n    const source = ctx.createBufferSource();\n    source.buffer = buffer;\n    source.connect(ctx.destination);\n\n    const now = ctx.currentTime;\n    if (this.nextStartTime === 0) {\n      // Fresh start (first audio of this session, or after flush).\n      this.nextStartTime = now;\n    } else if (this.nextStartTime < now) {\n      // Underrun: the playhead caught up to the queue. Snap to now (a small,\n      // unavoidable gap) and, when a prebuffer is configured, re-enter buffering\n      // so subsequent chunks re-accumulate before scheduling — collapsing a\n      // train of clicks into a single rebuffer.\n      this.nextStartTime = now;\n      if (this.waterlineSamples > 0) this.buffering = true;\n    }\n    source.start(this.nextStartTime);\n    this.nextStartTime += buffer.duration;\n\n    this.activeSources.push(source);\n    this.pendingCount++;\n    this.playing = true;\n\n    if (!this.started) {\n      this.started = true;\n      const cbs = this.startedCallbacks.slice();\n      this.startedCallbacks = [];\n      for (const cb of cbs) cb();\n    }\n\n    source.onended = () => {\n      const idx = this.activeSources.indexOf(source);\n      if (idx !== -1) this.activeSources.splice(idx, 1);\n      this.pendingCount--;\n      this.checkFinished();\n    };\n  }\n\n  private checkFinished(): void {\n    // Fire once the stream has ended and nothing is scheduled or held. No\n    // `playing` precondition: an empty reply (markStreamEnd with no audio) must\n    // still resolve to idle, matching the worklet's immediate 'drained'.\n    if (\n      this.streamEnded &&\n      this.pendingCount <= 0 &&\n      this.pendingBuffers.length === 0\n    ) {\n      this.playing = false;\n      this.streamEnded = false;\n      const cbs = this.finishedCallbacks.slice();\n      this.finishedCallbacks = [];\n      for (const cb of cbs) cb();\n    }\n  }\n\n  /**\n   * Convert 16-bit signed LE PCM to Float32 samples in [-1, 1].\n   */\n  private pcmToFloat32(pcmData: Uint8Array): Float32Array {\n    // 2 bytes per sample (16-bit)\n    const numSamples = Math.floor(pcmData.length / 2);\n    const float32 = new Float32Array(numSamples);\n    const view = new DataView(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength);\n\n    for (let i = 0; i < numSamples; i++) {\n      const int16 = view.getInt16(i * 2, true); // little-endian\n      float32[i] = int16 / 32768;\n    }\n\n    return float32;\n  }\n}\n","// Runtype Speech Engine (streaming TTS)\n//\n// Built-in `SpeechEngine` that backs the per-message \"Read aloud\" action (and\n// the auto-speak path) with Runtype-hosted text-to-speech. It is the client\n// half of the \"Option A\" HTTP synthesize design: a stateless\n//\n//   POST {host}/v1/agents/:agentId/speak  ->  streamed PCM16 / 24 kHz / mono\n//\n// The `clientToken` is browser-safe (same one the chat widget uses, scoped by\n// `allowedOrigins`), so this calls Runtype directly from the page — no proxy.\n// The streamed PCM is fed chunk-by-chunk into a {@link PcmStreamPlayer}, so this\n// engine stays tiny: fetch -> enqueue chunks -> markStreamEnd. The player owns\n// prebuffering, gapless scheduling, graceful underrun, and pause/resume.\n//\n// By default the player is the in-bundle, main-thread `AudioPlaybackManager`\n// (no AudioWorklet module — keeps the main bundle lean). Consumers who want the\n// jitter-buffered AudioWorklet player inject `createPcmStreamPlayer` from\n// `@runtypelabs/persona/voice-worklet-player` via `createPlaybackEngine` (config:\n// `textToSpeech.createPlaybackEngine`); the worklet then lands in their bundle.\n//\n// Wired automatically by `textToSpeech: { provider: 'runtype' }` (see\n// `session.ts`), which derives `host`/`agentId`/`clientToken` from the widget\n// config and — unless `browserFallback: false` — wraps this in a\n// `FallbackSpeechEngine` so a missing endpoint or transient failure falls back\n// to the browser voice instead of erroring.\n\nimport type {\n  PcmStreamPlayer,\n  SpeechCallbacks,\n  SpeechEngine,\n  SpeechRequest,\n} from \"../types\";\nimport { AudioPlaybackManager } from \"./audio-playback-manager\";\n\nexport interface RuntypeSpeechEngineOptions {\n  /**\n   * Runtype API host, e.g. `https://api.runtype.com` (typically the widget's\n   * `apiUrl`). A trailing slash is tolerated.\n   */\n  host: string;\n  /** Agent whose configured voice synthesizes the text. */\n  agentId: string;\n  /** Browser-safe client token — the same one the chat widget uses. */\n  clientToken: string;\n  /**\n   * Default voice id, used when a `SpeechRequest` doesn't carry its own. When\n   * omitted the agent's configured voice is used.\n   */\n  voice?: string;\n  /**\n   * Audio (ms) the player buffers before the first sample and after an\n   * underrun. Runtype streams steadily, so the default (200) keeps first sound\n   * close to time-to-first-byte while still riding out small hiccups. Applies to\n   * the default {@link AudioPlaybackManager}; a custom `createPlaybackEngine` is\n   * responsible for its own prebuffer.\n   */\n  prebufferMs?: number;\n  /**\n   * Factory for the streaming PCM player. Defaults to the in-bundle, main-thread\n   * {@link AudioPlaybackManager} (with `prebufferMs`). Pass `createPcmStreamPlayer`\n   * from `@runtypelabs/persona/voice-worklet-player` for the jitter-buffered\n   * AudioWorklet player (it then ships in your bundle, not Persona's). May be\n   * async — it is resolved on first playback, inside the user gesture.\n   */\n  createPlaybackEngine?: () => PcmStreamPlayer | Promise<PcmStreamPlayer>;\n  /**\n   * Optional hook for surfacing fetch/stream failures (a missing endpoint, an\n   * expired token, an upstream 4xx) to a log or telemetry. The widget itself\n   * only returns the read-aloud button to idle (or, with a fallback engine,\n   * silently switches to the browser voice), so without this the reason is\n   * invisible.\n   */\n  onError?: (error: Error) => void;\n}\n\n/** Strip a trailing slash so `${host}/v1/...` never doubles up. */\nfunction normalizeHost(host: string): string {\n  return host.replace(/\\/+$/, \"\");\n}\n\n/** Streaming `SpeechEngine` backed by Runtype's `/v1/agents/:id/speak`. */\nexport class RuntypeSpeechEngine implements SpeechEngine {\n  readonly id = \"runtype-tts\";\n  // The PCM player pauses/resumes via AudioContext.suspend() — solid, unlike\n  // speechSynthesis.pause().\n  readonly supportsPause = true;\n\n  private player: PcmStreamPlayer | null = null;\n  private playerPromise: Promise<PcmStreamPlayer> | null = null;\n  // Bumped on every speak()/stop() so a superseded request's async callbacks and\n  // its in-flight stream read loop become no-ops.\n  private generation = 0;\n\n  constructor(private readonly opts: RuntypeSpeechEngineOptions) {}\n\n  // Create one player lazily (a worklet engine's addModule is async), then reuse\n  // it across speaks; flush() between replies clears the queue without tearing\n  // down the AudioContext. Defaults to the in-bundle AudioPlaybackManager.\n  private ensurePlayer(): Promise<PcmStreamPlayer> {\n    return (this.playerPromise ??= Promise.resolve(\n      this.opts.createPlaybackEngine\n        ? this.opts.createPlaybackEngine()\n        : new AudioPlaybackManager(24000, {\n            prebufferMs: this.opts.prebufferMs ?? 200,\n          }),\n    ).then((player) => (this.player = player)));\n  }\n\n  speak(request: SpeechRequest, callbacks: SpeechCallbacks): void {\n    const gen = ++this.generation;\n    // Run the async fetch/stream without making speak() itself async — the\n    // widget surfaces the time until audio starts as the \"loading\" state.\n    void this.run(gen, request, callbacks);\n  }\n\n  private async run(\n    gen: number,\n    request: SpeechRequest,\n    callbacks: SpeechCallbacks,\n  ): Promise<void> {\n    try {\n      const player = await this.ensurePlayer();\n      if (gen !== this.generation) return; // superseded while the worklet booted\n      player.flush(); // drop any prior playback (and its callbacks)\n      player.resume(); // clear a prior pause so this reply isn't stuck suspended\n\n      // Drive read-aloud state from the player, not from chunk arrival: onStarted\n      // fires when the prebuffer fills and audio is actually audible (loading ->\n      // playing); onFinished when it drains (-> idle). An empty stream produces\n      // no onStarted and an immediate onFinished, so the UI skips straight back\n      // to idle without a phantom \"playing\".\n      player.onStarted(() => {\n        if (gen === this.generation) callbacks.onStart?.();\n      });\n      player.onFinished(() => {\n        if (gen === this.generation) callbacks.onEnd?.();\n      });\n\n      const url = `${normalizeHost(this.opts.host)}/v1/agents/${encodeURIComponent(\n        this.opts.agentId,\n      )}/speak`;\n      const res = await fetch(url, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          // Match Runtype's client-token auth convention. Never placed in the\n          // URL/query string.\n          Authorization: `Bearer ${this.opts.clientToken}`,\n        },\n        body: JSON.stringify({\n          text: request.text,\n          voice: request.voice ?? this.opts.voice,\n          format: \"pcm\",\n        }),\n      });\n      if (gen !== this.generation) return; // superseded while awaiting headers\n      if (!res.ok || !res.body) throw new Error(await describeError(res));\n\n      const reader = res.body.getReader();\n      for (;;) {\n        const { done, value } = await reader.read();\n        if (gen !== this.generation) {\n          // A newer speak()/stop() won — stop pulling and release the stream.\n          await reader.cancel().catch(() => {});\n          return;\n        }\n        if (done) break;\n        if (value && value.byteLength > 0) player.enqueue(value);\n      }\n\n      player.markStreamEnd();\n    } catch (err) {\n      if (gen !== this.generation) return; // error from a superseded request\n      const error = err instanceof Error ? err : new Error(String(err));\n      this.opts.onError?.(error); // surface the reason (log, telemetry, …)\n      callbacks.onError?.(error); // and let the widget (or fallback) react\n    }\n  }\n\n  pause(): void {\n    this.player?.pause();\n  }\n\n  resume(): void {\n    this.player?.resume();\n  }\n\n  stop(): void {\n    this.generation++; // invalidate any in-flight stream + pending onFinished\n    this.player?.flush();\n  }\n\n  destroy(): void {\n    this.generation++;\n    void this.player?.destroy();\n    this.player = null;\n    this.playerPromise = null;\n  }\n}\n\n/** Best-effort human-readable message from a non-OK speak response. */\nasync function describeError(res: Response): Promise<string> {\n  try {\n    const data = (await res.json()) as { error?: string; detail?: string };\n    return data.detail\n      ? `${data.error ?? `Runtype TTS ${res.status}`}: ${data.detail}`\n      : data.error ?? `Runtype TTS request failed (${res.status})`;\n  } catch {\n    return `Runtype TTS request failed (${res.status})`;\n  }\n}\n","// Fallback Speech Engine\n//\n// Composes a primary `SpeechEngine` with a fallback one (typically a hosted\n// engine + the browser Web Speech API). If the primary fails *before* any audio\n// is audible — a missing/404 endpoint, a network error, an auth failure — the\n// fallback transparently takes over for that utterance, so the \"Read aloud\"\n// button is never left broken. A failure that happens *after* playback has\n// started (a mid-stream drop) is surfaced as a real error instead of restarting\n// from the top.\n//\n// This is what makes `textToSpeech: { provider: 'runtype' }` safe to ship before\n// (or independently of) the Runtype TTS endpoint: it speaks with the browser\n// voice today and auto-upgrades to Runtype voices the moment the endpoint\n// answers. Set `browserFallback: false` to opt out and surface Runtype errors.\n\nimport type { SpeechCallbacks, SpeechEngine, SpeechRequest } from \"../types\";\n\nexport interface FallbackSpeechEngineOptions {\n  /**\n   * Called once when the primary engine fails before audio starts and the\n   * fallback takes over — so a silent downgrade is still observable in dev/\n   * telemetry even though the user keeps hearing speech.\n   */\n  onFallback?: (error: Error) => void;\n}\n\n/** A `SpeechEngine` that falls back from `primary` to `fallback` per utterance. */\nexport class FallbackSpeechEngine implements SpeechEngine {\n  readonly id = \"fallback\";\n\n  // Whichever engine is currently driving playback, so pause/resume/stop route\n  // to the right one after a fallback has (or hasn't) kicked in.\n  private active: SpeechEngine;\n\n  constructor(\n    private readonly primary: SpeechEngine,\n    private readonly fallback: SpeechEngine,\n    private readonly options: FallbackSpeechEngineOptions = {},\n  ) {\n    this.active = primary;\n  }\n\n  // Pause/resume only matters once something is playing, and both built-in\n  // engines support it; report the active engine's capability.\n  get supportsPause(): boolean {\n    return this.active.supportsPause;\n  }\n\n  speak(request: SpeechRequest, callbacks: SpeechCallbacks): void {\n    this.active = this.primary;\n    let started = false;\n\n    this.primary.speak(request, {\n      onStart: () => {\n        started = true;\n        callbacks.onStart?.();\n      },\n      onEnd: () => callbacks.onEnd?.(),\n      onError: (error) => {\n        // A failure once audio is playing is a genuine error — don't restart.\n        if (started) {\n          callbacks.onError?.(error);\n          return;\n        }\n        // Pre-start failure: silently hand the utterance to the fallback.\n        this.options.onFallback?.(error);\n        this.active = this.fallback;\n        this.fallback.speak(request, callbacks);\n      },\n    });\n  }\n\n  pause(): void {\n    this.active.pause();\n  }\n\n  resume(): void {\n    this.active.resume();\n  }\n\n  stop(): void {\n    this.active.stop();\n  }\n\n  destroy(): void {\n    this.primary.destroy?.();\n    this.fallback.destroy?.();\n  }\n}\n","// Standalone entry for the deferred Runtype TTS chunk (`dist/runtype-tts.js`).\n//\n// Bundles the hosted read-aloud engine, its browser-fallback wrapper, and the\n// `AudioPlaybackManager` they depend on, so the IIFE/CDN build can load the\n// whole `provider: 'runtype'` read-aloud path on demand (kept out of\n// `index.global.js`). `session.ts` resolves this module via\n// `runtype-tts-loader.ts`; the loader is overridden in `index-global.ts` to\n// fetch this chunk from a sibling URL. See `runtype-tts-loader.ts`.\nexport { RuntypeSpeechEngine } from \"./runtype-speech-engine\";\nexport { FallbackSpeechEngine } from \"./fallback-speech-engine\";\n","// Durable-session reconnect orchestration (the cold path).\n//\n// This module holds the heavy reconnect machinery — the bounded backoff loop,\n// the focus/online wake listeners, and the give-up finalizer. It is only ever\n// reached through a dynamic `import(\"./session-reconnect\")` in `session.ts`,\n// gated on `config.reconnectStream`, so a widget that never opts into durable\n// reconnect (the overwhelming majority, including the theme-editor preview)\n// never loads it. With code-splitting enabled on the ESM subpath builds, that\n// keeps this code out of those bundles entirely.\n//\n// IMPORTANT: this module must NOT statically import from `session.ts` (that\n// would form a runtime cycle and pull the whole session back into the split\n// chunk, defeating the point). It reaches everything it needs through the\n// `ReconnectHost` interface the session hands it. Type-only imports are erased\n// at build time, so importing `AgentWidgetSessionStatus` as a type is fine.\n\nimport type {\n  AgentWidgetConfig,\n  AgentWidgetMessage,\n  ResumableHandle,\n} from \"./types\";\nimport type { AgentWidgetSessionStatus } from \"./session\";\n\n/**\n * The narrow surface the reconnect loop needs from the owning session. The\n * session builds this once (lazily, alongside the controller) so the loop can\n * read/advance the resume handle, drive status/streaming, and pipe the resumed\n * stream back through the session's own `connectStream`.\n */\nexport interface ReconnectHost {\n  readonly config: AgentWidgetConfig;\n  getResumable(): ResumableHandle | null;\n  clearResumable(): void;\n  getStatus(): AgentWidgetSessionStatus;\n  setStatus(status: AgentWidgetSessionStatus): void;\n  setStreaming(streaming: boolean): void;\n  setReconnecting(value: boolean): void;\n  setAbortController(controller: AbortController | null): void;\n  getMessages(): AgentWidgetMessage[];\n  notifyMessagesChanged(): void;\n  /** Pipe a resumed SSE body through the session, filling the same bubble. */\n  resumeConnect(\n    body: ReadableStream<Uint8Array>,\n    assistantMessageId: string,\n    seedContent: string\n  ): Promise<void>;\n  appendMessage(message: AgentWidgetMessage): void;\n  nextSequence(): number;\n  emitReconnect(event: {\n    phase: \"paused\" | \"resuming\" | \"resumed\";\n    handle: ResumableHandle;\n    attempt?: number;\n  }): void;\n  /** Build the give-up bubble content (honors `config.errorMessage`). */\n  buildErrorContent(message: string): string;\n  onError(error: Error): void;\n}\n\nexport interface ReconnectController {\n  /** Start the backoff loop. The session has already flipped the visible state\n   *  (streaming + `resuming` status + `paused` event) synchronously. */\n  begin(): void;\n  /** Cancel any pending backoff and drop the wake listeners. */\n  teardown(): void;\n  /** Short-circuit the current backoff sleep (focus/online or manual retry). */\n  wake(): void;\n}\n\nconst DEFAULT_BACKOFF_MS = [1000, 2000, 4000, 8000, 8000];\n\nexport function createReconnectController(\n  host: ReconnectHost\n): ReconnectController {\n  // 1-based attempt counter for the active run (0 = not running).\n  let attempt = 0;\n  let waitResolve: (() => void) | null = null;\n  let waitTimer: ReturnType<typeof setTimeout> | null = null;\n  let listenersAttached = false;\n\n  const wake = (): void => {\n    if (waitTimer) {\n      clearTimeout(waitTimer);\n      waitTimer = null;\n    }\n    if (waitResolve) {\n      const resolve = waitResolve;\n      waitResolve = null;\n      resolve();\n    }\n  };\n\n  // Short-circuit the active backoff sleep, but only while a reconnect is\n  // genuinely in flight (`resuming`/`paused`).\n  const wakeIfActive = (): void => {\n    const status = host.getStatus();\n    if (status !== \"resuming\" && status !== \"paused\") return;\n    wake();\n  };\n\n  // `visibilitychange` fires on both show and hide; only wake on the show\n  // transition (ignore the tab being backgrounded).\n  const handleVisibility = (): void => {\n    if (\n      typeof document !== \"undefined\" &&\n      document.visibilityState === \"hidden\"\n    ) {\n      return;\n    }\n    wakeIfActive();\n  };\n\n  // Connectivity restored: retry immediately, even in a background tab — the\n  // whole point of the `online` listener is to react before the backoff timer,\n  // so it must NOT inherit the visibility guard above.\n  const handleOnline = (): void => {\n    wakeIfActive();\n  };\n\n  const attachListeners = (): void => {\n    if (listenersAttached) return;\n    if (typeof document !== \"undefined\") {\n      document.addEventListener(\"visibilitychange\", handleVisibility);\n    }\n    if (typeof window !== \"undefined\") {\n      window.addEventListener(\"online\", handleOnline);\n    }\n    listenersAttached = true;\n  };\n\n  const detachListeners = (): void => {\n    if (!listenersAttached) return;\n    if (typeof document !== \"undefined\") {\n      document.removeEventListener(\"visibilitychange\", handleVisibility);\n    }\n    if (typeof window !== \"undefined\") {\n      window.removeEventListener(\"online\", handleOnline);\n    }\n    listenersAttached = false;\n  };\n\n  const waitBackoff = (ms: number): Promise<void> =>\n    new Promise<void>((resolve) => {\n      waitResolve = resolve;\n      waitTimer = setTimeout(() => {\n        waitTimer = null;\n        waitResolve = null;\n        resolve();\n      }, ms);\n    });\n\n  /** Give up after exhausting attempts: finalize the bubble and surface error. */\n  const finalizeFailure = (): void => {\n    const handle = host.getResumable();\n    host.clearResumable();\n    host.setReconnecting(false);\n    attempt = 0;\n    detachListeners();\n    host.setAbortController(null);\n\n    let changed = false;\n    for (const msg of host.getMessages()) {\n      if (msg.streaming) {\n        msg.streaming = false;\n        changed = true;\n      }\n    }\n\n    const content = host.buildErrorContent(\n      \"Connection lost and the response could not be resumed.\"\n    );\n    if (content) {\n      host.appendMessage({\n        id: `reconnect-failed-${handle?.executionId ?? host.nextSequence()}`,\n        role: \"assistant\",\n        content,\n        createdAt: new Date().toISOString(),\n        streaming: false,\n        sequence: host.nextSequence(),\n      });\n    } else if (changed) {\n      host.notifyMessagesChanged();\n    }\n\n    host.setStreaming(false);\n    host.setStatus(\"idle\");\n    host.onError(new Error(\"Durable session reconnect failed.\"));\n  };\n\n  const runLoop = async (): Promise<void> => {\n    const backoff = host.config.reconnect?.backoffMs ?? DEFAULT_BACKOFF_MS;\n    const maxAttempts = host.config.reconnect?.maxAttempts ?? backoff.length;\n    const reconnectStream = host.config.reconnectStream;\n    if (!reconnectStream) {\n      host.setReconnecting(false);\n      return;\n    }\n\n    while (host.getResumable() && attempt < maxAttempts) {\n      attempt += 1;\n      // An attempt is in flight. No-op for attempt 1 (the session's synchronous\n      // `beginReconnect` already set `resuming`); flips back from `paused` after\n      // each backoff wait.\n      host.setStatus(\"resuming\");\n      const handle = host.getResumable()!;\n      const before = handle.lastEventId;\n      const controller = new AbortController();\n      host.setAbortController(controller);\n      host.emitReconnect({ phase: \"resuming\", handle, attempt });\n\n      let body: ReadableStream<Uint8Array> | null = null;\n      try {\n        const res = await reconnectStream({\n          executionId: handle.executionId,\n          after: handle.lastEventId,\n          signal: controller.signal,\n        });\n        if (res && res.ok && res.body) body = res.body;\n      } catch {\n        body = null;\n      }\n      if (controller.signal.aborted) return; // torn down (cancel/new turn)\n\n      if (body) {\n        // Seed the resume with the text already shown so post-cursor deltas\n        // append rather than clobber the bubble.\n        const found = host\n          .getMessages()\n          .find((m) => m.id === handle.assistantMessageId)?.content;\n        const seedContent = typeof found === \"string\" ? found : \"\";\n        try {\n          await host.resumeConnect(body, handle.assistantMessageId, seedContent);\n        } catch {\n          // resumeConnect swallows resume-stream errors while `resuming`;\n          // anything escaping here just falls through to backoff/retry.\n        }\n        if (controller.signal.aborted) return;\n        // Reconnect reached a terminal: the idle(terminal)/error handler cleared\n        // the handle. We're done.\n        if (!host.getResumable()) {\n          host.setReconnecting(false);\n          attempt = 0;\n          detachListeners();\n          host.emitReconnect({ phase: \"resumed\", handle });\n          return;\n        }\n        // Made forward progress before dropping again → reset the attempt\n        // budget so a long, flaky run isn't capped by total drops.\n        if (host.getResumable()!.lastEventId !== before) attempt = 0;\n      }\n\n      if (host.getResumable() && attempt < maxAttempts) {\n        // Dropped and waiting to retry: surface `paused` so\n        // `statusIndicator.pausedText` renders during the backoff sleep.\n        host.setStatus(\"paused\");\n        await waitBackoff(\n          backoff[Math.min(attempt - 1, backoff.length - 1)] ?? 1000\n        );\n        if (controller.signal.aborted) return;\n      }\n    }\n\n    if (host.getResumable()) finalizeFailure();\n  };\n\n  return {\n    begin(): void {\n      attempt = 0;\n      attachListeners();\n      void runLoop();\n    },\n    teardown(): void {\n      if (waitTimer) {\n        clearTimeout(waitTimer);\n        waitTimer = null;\n      }\n      waitResolve = null;\n      attempt = 0;\n      detachListeners();\n    },\n    wake,\n  };\n}\n","/**\n * npm package entry (`@runtypelabs/persona`).\n *\n * This is a thin barrel over `index-core.ts` (the shared public API) that adds\n * back the **dev/config-tool-only** helpers: `generateCodeSnippet` and\n * `createDemoCarousel`. Those are kept out of `index-core.ts` so the IIFE/CDN\n * build (`index-global.ts`, which re-exports from `index-core.ts`) doesn't ship\n * them: a running widget never needs them, only build-time/demo tooling does.\n *\n * Net effect: npm consumers get the full API (unchanged), while the script-tag\n * `window.AgentWidget` global no longer exposes `generateCodeSnippet` /\n * `createDemoCarousel`.\n */\n\n// Register `marked` + `dompurify` synchronously for the bundled npm build so\n// the synchronous markdown/sanitize API renders on first paint. The IIFE/CDN\n// entry (`index-global.ts`) does NOT import this; it lazy-loads the parsers\n// from the `markdown-parsers.js` chunk instead. Must run before any render.\nimport \"./markdown-parsers-eager\";\n\n// Full public API (everything except the two dev-only helpers below).\nexport * from \"./index-core\";\nexport { default } from \"./index-core\";\n\n// Dev / config-tool helper: generate install snippets from a widget config.\nexport { generateCodeSnippet } from \"./utils/code-generators\";\nexport type {\n  CodeFormat,\n  CodeGeneratorHooks,\n  CodeGeneratorOptions\n} from \"./utils/code-generators\";\n\n// Demo-only component: the examples' showcase carousel.\nexport { createDemoCarousel } from \"./components/demo-carousel\";\nexport type {\n  DemoCarouselItem,\n  DemoCarouselOptions,\n  DemoCarouselHandle\n} from \"./components/demo-carousel\";\n\n// Accessible roving-tabindex tablist helper for custom artifact tab bars\n// (features.artifacts.renderTabBar).\nexport { createRovingTablist } from \"./utils/roving-tablist\";\nexport type {\n  RovingTablistController,\n  RovingTablistOptions\n} from \"./utils/roving-tablist\";\n","/**\n * Eager markdown-parser registration for the bundled (ESM / CJS) builds.\n *\n * npm consumers bundle `marked` and `dompurify` directly anyway, so there is no\n * benefit to deferring them — and the public `markdownPostprocessor()` /\n * `createDefaultSanitizer()` API is synchronous. Importing this module for its\n * side effect registers both parsers up front so `getMarkdownParsersSync()`\n * resolves them on the first render.\n *\n * This module is intentionally NOT reachable from `index-global.ts` (the IIFE\n * entry): it statically imports `marked` + `dompurify`, so pulling it into the\n * CDN bundle would defeat the lazy `markdown-parsers.js` chunk. It is imported\n * only from `index.ts` (the npm barrel) and from unit tests that exercise the\n * synchronous parsers directly.\n */\nimport { Marked } from \"marked\";\nimport DOMPurify from \"dompurify\";\nimport { provideMarkdownParsers } from \"./markdown-parsers-loader\";\n\nprovideMarkdownParsers({ Marked, DOMPurify });\n","import type { Marked } from \"marked\";\nimport type DOMPurify from \"dompurify\";\n\nexport type MarkdownParsersModule = {\n  Marked: typeof Marked;\n  DOMPurify: typeof DOMPurify;\n};\n\nlet loader: (() => Promise<MarkdownParsersModule>) | null = null;\nlet moduleCache: MarkdownParsersModule | null = null;\nlet loadPromise: Promise<MarkdownParsersModule> | null = null;\n\n// Surfaces that want to self-heal when the lazy `markdown-parsers.js` chunk\n// lands. See `onMarkdownParsersReady` below for why this is centralized.\nconst readySubscribers = new Set<() => void>();\n\n// Flip `moduleCache` and fan out to everyone waiting to re-render, exactly once\n// per subscriber. Called from BOTH the eager (`provideMarkdownParsers`) and lazy\n// (`loadMarkdownParsers`) paths so a subscriber can't miss the transition.\nconst markParsersReady = (mod: MarkdownParsersModule): MarkdownParsersModule => {\n  moduleCache = mod;\n  // Snapshot + clear first: fire-once semantics, and a callback that re-subscribes\n  // (it won't — `onMarkdownParsersReady` no-ops once ready) can't loop.\n  const subs = [...readySubscribers];\n  readySubscribers.clear();\n  for (const cb of subs) {\n    // One bad subscriber must not starve the others (or leave messages escaped).\n    try {\n      cb();\n    } catch {\n      /* subscriber threw: swallow so the remaining re-renders still run */\n    }\n  }\n  return mod;\n};\n\nexport const setMarkdownParsersLoader = (l: () => Promise<MarkdownParsersModule>) => {\n  loader = l;\n};\n\n/**\n * Register `cb` to run once the markdown parsers (marked + DOMPurify) become\n * available, i.e. when the lazy `markdown-parsers.js` chunk resolves on the\n * IIFE/CDN build. Returns an unsubscribe function.\n *\n * This exists so every markdown render surface (chat messages, artifact pane,\n * and any future one) shares a SINGLE self-heal path instead of each wiring its\n * own `loadMarkdownParsers().then(reRender)`. Before this, a new surface that\n * forgot to do that rendered escaped plain text until a user interaction forced\n * a re-render — the recurring first-render bug (chat messages, then the artifact\n * pane) this centralizes away.\n *\n * If the parsers are ALREADY loaded, `cb` is not scheduled and a no-op\n * unsubscribe is returned: the caller's first render already used real markdown,\n * so there is nothing to heal (this is the ESM/CJS build's steady state, and the\n * CDN build's state after the first chunk load). Registering also kicks the load\n * so a surface that renders before anything else triggers it still heals.\n * Fires at most once per subscription.\n */\nexport const onMarkdownParsersReady = (cb: () => void): (() => void) => {\n  if (moduleCache) return () => {};\n  readySubscribers.add(cb);\n  // Ensure the chunk is actually being fetched; harmless if already in flight.\n  // Swallow rejection here — on failure the subscriber stays registered (see\n  // `onLoadFailure`) and the surface keeps its escaped fallback until a retry\n  // succeeds; this catch only avoids an unhandled rejection.\n  void loadMarkdownParsers().catch(() => {});\n  return () => {\n    readySubscribers.delete(cb);\n  };\n};\n\n/**\n * Register the parsers synchronously. Used by the ESM/CJS build (where `marked`\n * and `dompurify` are bundled directly via `markdown-parsers-eager.ts`), so\n * `getMarkdownParsersSync()` returns them on the very first render and the\n * synchronous `markdownPostprocessor` / `createDefaultSanitizer` API keeps\n * working without an async round-trip. The IIFE/CDN build never calls this;\n * it lazy-loads the `markdown-parsers.js` chunk instead.\n */\nexport const provideMarkdownParsers = (mod: MarkdownParsersModule): void => {\n  markParsersReady(mod);\n};\n\n// On a failed chunk load (404, ad blocker, offline), don't cache the rejected\n// promise: reset it so a later subscribe/load call can retry. Pending\n// subscribers are KEPT — a surface that rendered escaped during the failed\n// attempt must still heal when a retry (kicked by a new subscriber or an\n// explicit loadMarkdownParsers() call) succeeds; dropping them here left that\n// surface escaped forever after a transient failure. They're only retained\n// while the parsers stay unloaded — success releases them (fire-once in\n// `markParsersReady`), and callers can unsubscribe. On a permanently failing\n// setup they persist for the page's life, which is the price of healing.\n// Re-throw so awaiters of loadMarkdownParsers() still observe the rejection.\nconst onLoadFailure = (err: unknown): never => {\n  loadPromise = null;\n  throw err;\n};\n\nexport const loadMarkdownParsers = (): Promise<MarkdownParsersModule> => {\n  if (moduleCache) return Promise.resolve(moduleCache);\n  if (loadPromise) return loadPromise;\n  if (!loader) {\n    // Fallback for regular ESM/CJS consumers (they import directly)\n    loadPromise = import(\"./markdown-parsers-entry\").then(markParsersReady, onLoadFailure);\n    return loadPromise;\n  }\n  loadPromise = loader().then(markParsersReady, onLoadFailure);\n  return loadPromise;\n};\n\nexport const getMarkdownParsersSync = (): MarkdownParsersModule | null => {\n  return moduleCache;\n};\n","import type { RendererObject } from \"marked\";\nimport type { AgentWidgetMarkdownConfig, AgentWidgetMarkdownRendererOverrides, AgentWidgetMarkdownOptions } from \"./types\";\nimport { getMarkdownParsersSync } from \"./markdown-parsers-loader\";\n\n/**\n * Options for creating a markdown processor\n */\nexport type MarkdownProcessorOptions = {\n  /** Marked parsing options */\n  markedOptions?: AgentWidgetMarkdownOptions;\n  /** Custom renderer overrides */\n  renderer?: AgentWidgetMarkdownRendererOverrides;\n};\n\n/**\n * Converts AgentWidgetMarkdownRendererOverrides to marked's RendererObject format\n */\nconst convertRendererOverrides = (\n  overrides?: AgentWidgetMarkdownRendererOverrides\n): Partial<RendererObject> | undefined => {\n  if (!overrides) return undefined;\n  \n  // The token-based API in marked v12+ matches our type definitions\n  // We can pass through the overrides directly\n  return overrides as Partial<RendererObject>;\n};\n\n/**\n * Creates a configured markdown processor with custom options and renderers.\n * \n * @param options - Configuration options for the markdown processor\n * @returns A function that converts markdown text to HTML\n * \n * @example\n * ```typescript\n * // Basic usage with defaults\n * const processor = createMarkdownProcessor();\n * const html = processor(\"# Hello World\");\n * \n * // With custom options\n * const processor = createMarkdownProcessor({\n *   markedOptions: { gfm: true, breaks: true },\n *   renderer: {\n *     link(token) {\n *       return `<a href=\"${token.href}\" target=\"_blank\">${token.text}</a>`;\n *     }\n *   }\n * });\n * ```\n */\nexport const createMarkdownProcessor = (options?: MarkdownProcessorOptions) => {\n  let markedInstance: any = null;\n\n  return (text: string): string => {\n    const parsers = getMarkdownParsersSync();\n    if (!parsers) {\n      // If the markdown parser hasn't loaded yet, fall back to plain text with HTML escaped.\n      // The widget will re-render automatically once the parser finishes loading.\n      return escapeHtml(text);\n    }\n    \n    if (!markedInstance) {\n      const { Marked } = parsers;\n      const opts = options?.markedOptions;\n      markedInstance = new Marked({\n        gfm: opts?.gfm ?? true,\n        breaks: opts?.breaks ?? true,\n        pedantic: opts?.pedantic,\n        silent: opts?.silent,\n      });\n      \n      const rendererOverrides = convertRendererOverrides(options?.renderer);\n      if (rendererOverrides) {\n        markedInstance.use({ renderer: rendererOverrides });\n      }\n    }\n    \n    return markedInstance.parse(text) as string;\n  };\n};\n\n/**\n * Creates a markdown processor from AgentWidgetMarkdownConfig.\n * This is a convenience function that maps the widget config to processor options.\n * \n * @param config - The markdown configuration from widget config\n * @returns A function that converts markdown text to HTML\n */\nexport const createMarkdownProcessorFromConfig = (config?: AgentWidgetMarkdownConfig) => {\n  if (!config) {\n    return createMarkdownProcessor();\n  }\n  \n  return createMarkdownProcessor({\n    markedOptions: config.options,\n    renderer: config.renderer,\n  });\n};\n\n// Create default markdown processor instance\nconst defaultMarkdownProcessor = createMarkdownProcessor();\n\n/**\n * Basic markdown renderer using default settings.\n * Remember to sanitize the returned HTML if you render untrusted content in your host page.\n * \n * For custom configuration, use `createMarkdownProcessor()` or `createMarkdownProcessorFromConfig()`.\n */\nexport const markdownPostprocessor = (text: string): string => {\n  return defaultMarkdownProcessor(text);\n};\n\n/**\n * Escapes HTML entities. Used as the default safe renderer.\n */\nexport const escapeHtml = (text: string): string =>\n  text\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#39;\");\n\nconst escapeAttribute = (value: string) =>\n  value.replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n\nconst makeToken = (idx: number) => `%%FORM_PLACEHOLDER_${idx}%%`;\n\nconst directiveReplacer = (source: string, placeholders: Array<{ token: string; type: string }>) => {\n  let working = source;\n\n  // JSON directive pattern e.g. <Directive>{\"component\":\"form\",\"type\":\"init\"}</Directive>\n  working = working.replace(/<Directive>([\\s\\S]*?)<\\/Directive>/gi, (match, jsonText) => {\n    try {\n      const parsed = JSON.parse(jsonText.trim());\n      if (parsed && typeof parsed === \"object\" && parsed.component === \"form\" && parsed.type) {\n        const token = makeToken(placeholders.length);\n        placeholders.push({ token, type: String(parsed.type) });\n        return token;\n      }\n    } catch (error) {\n      return match;\n    }\n    return match;\n  });\n\n  // XML-style directive e.g. <Form type=\"init\" />\n  working = working.replace(/<Form\\s+type=\"([^\"]+)\"\\s*\\/>/gi, (_, type) => {\n    const token = makeToken(placeholders.length);\n    placeholders.push({ token, type });\n    return token;\n  });\n\n  return working;\n};\n\n/**\n * Creates a directive postprocessor with custom markdown configuration.\n * Converts special directives (either `<Form type=\"init\" />` or\n * `<Directive>{\"component\":\"form\",\"type\":\"init\"}</Directive>`) into placeholder\n * elements that the widget upgrades after render. Remaining text is rendered as\n * Markdown with the provided configuration.\n * \n * @param markdownConfig - Optional markdown configuration\n * @returns A function that processes text with directives and markdown\n */\nexport const createDirectivePostprocessor = (markdownConfig?: AgentWidgetMarkdownConfig) => {\n  const processor = createMarkdownProcessorFromConfig(markdownConfig);\n  \n  return (text: string): string => {\n    const placeholders: Array<{ token: string; type: string }> = [];\n    const withTokens = directiveReplacer(text, placeholders);\n    let html = processor(withTokens);\n\n    placeholders.forEach(({ token, type }) => {\n      const tokenRegex = new RegExp(token.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\");\n      const safeType = escapeAttribute(type);\n      const replacement = `<div class=\"persona-form-directive\" data-tv-form=\"${safeType}\"></div>`;\n      html = html.replace(tokenRegex, replacement);\n    });\n\n    return html;\n  };\n};\n\n/**\n * Converts special directives (either `<Form type=\"init\" />` or\n * `<Directive>{\"component\":\"form\",\"type\":\"init\"}</Directive>`) into placeholder\n * elements that the widget upgrades after render. Remaining text is rendered as\n * Markdown using default settings.\n * \n * For custom markdown configuration, use `createDirectivePostprocessor()`.\n */\nexport const directivePostprocessor = (text: string): string => {\n  const placeholders: Array<{ token: string; type: string }> = [];\n  const withTokens = directiveReplacer(text, placeholders);\n  let html = markdownPostprocessor(withTokens);\n\n  placeholders.forEach(({ token, type }) => {\n    const tokenRegex = new RegExp(token.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"), \"g\");\n    const safeType = escapeAttribute(type);\n    const replacement = `<div class=\"persona-form-directive\" data-tv-form=\"${safeType}\"></div>`;\n    html = html.replace(tokenRegex, replacement);\n  });\n\n  return html;\n};\n","import type * as DOMPurifyType from \"dompurify\";\nimport { escapeHtml } from \"../postprocessors\";\nimport { getMarkdownParsersSync } from \"../markdown-parsers-loader\";\n\n/**\n * A function that sanitizes an HTML string, returning safe HTML.\n */\nexport type SanitizeFunction = (html: string) => string;\n\nconst DEFAULT_PURIFY_CONFIG: DOMPurifyType.Config = {\n  // Tags safe for markdown-rendered content\n  ALLOWED_TAGS: [\n    // Headings & structure\n    \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"p\", \"br\", \"hr\", \"div\", \"span\",\n    // Lists\n    \"ul\", \"ol\", \"li\", \"dl\", \"dt\", \"dd\",\n    // Inline formatting\n    \"strong\", \"em\", \"b\", \"i\", \"u\", \"s\", \"del\", \"ins\", \"mark\", \"small\", \"sub\", \"sup\",\n    \"abbr\", \"kbd\", \"var\", \"samp\", \"code\",\n    // Links & media\n    \"a\", \"img\",\n    // Block elements\n    \"blockquote\", \"pre\", \"details\", \"summary\",\n    // Tables\n    \"table\", \"thead\", \"tbody\", \"tfoot\", \"tr\", \"th\", \"td\", \"caption\", \"colgroup\", \"col\",\n    // Forms (used by widget directive system)\n    \"input\", \"label\", \"select\", \"option\", \"textarea\", \"button\",\n  ],\n  ALLOWED_ATTR: [\n    // Link/media attributes\n    \"href\", \"src\", \"alt\", \"title\", \"target\", \"rel\", \"loading\", \"width\", \"height\",\n    // Table attributes\n    \"colspan\", \"rowspan\", \"scope\",\n    // Styling & identity\n    \"class\", \"id\",\n    // Form attributes\n    \"type\", \"name\", \"value\", \"placeholder\", \"disabled\", \"checked\", \"for\",\n    // Accessibility\n    \"aria-label\", \"aria-hidden\", \"aria-expanded\", \"role\", \"tabindex\",\n    // Widget-internal data attributes\n    \"data-tv-form\", \"data-message-id\", \"data-persona-component-directive\",\n    \"data-preserve-animation\", \"data-persona-instance\",\n  ],\n};\n\n/** Raster image data URI pattern: blocks SVG and other non-image types. */\nconst SAFE_DATA_URI = /^data:image\\/(?:png|jpe?g|gif|webp|bmp|x-icon|avif)/i;\n\n/**\n * Creates the default DOMPurify-based sanitizer.\n * Uses the global window when available (browser).\n */\nexport const createDefaultSanitizer = (): SanitizeFunction => {\n  let purifyInstance: ReturnType<typeof DOMPurifyType.default> | null = null;\n\n  return (html: string): string => {\n    const parsers = getMarkdownParsersSync();\n    if (!parsers) {\n      // If DOMPurify hasn't loaded yet, fall back to escaping HTML completely\n      // to remain safe until the module is available.\n      return escapeHtml(html);\n    }\n    \n    if (!purifyInstance) {\n      const { DOMPurify } = parsers;\n      // DOMPurify needs a DOM context. In the browser, pass `window`.\n      // The widget only runs in browsers, so `window` is always available at runtime.\n      purifyInstance = DOMPurify(typeof window !== \"undefined\" ? window : (undefined as never));\n\n      // Hook: strip data:image/svg+xml and other unsafe data: URIs from src/href\n      purifyInstance.addHook(\"uponSanitizeAttribute\", (_node, data) => {\n        if (data.attrName === \"src\" || data.attrName === \"href\") {\n          const val = data.attrValue;\n          if (val.toLowerCase().startsWith(\"data:\") && !SAFE_DATA_URI.test(val)) {\n            data.attrValue = \"\";\n            data.keepAttr = false;\n          }\n        }\n      });\n    }\n\n    return purifyInstance.sanitize(html, DEFAULT_PURIFY_CONFIG) as string;\n  };\n};\n\n/**\n * Resolves a `sanitize` config value into a concrete function or null.\n *\n * - `undefined` / `true` → built-in DOMPurify sanitizer\n * - `false` → `null` (no sanitization)\n * - custom function → returned as-is\n */\nexport const resolveSanitizer = (\n  option: boolean | SanitizeFunction | undefined,\n): SanitizeFunction | null => {\n  if (option === false) return null;\n  if (typeof option === \"function\") return option;\n  return createDefaultSanitizer();\n};\n","/**\n * Streaming markdown table stabilizer (Telegram-style space reservation).\n *\n * During SSE streaming the full accumulated markdown is re-parsed by `marked`\n * on every chunk. For GFM tables that produces two jarring jolts:\n *\n *  1. The paragraph→table flip. GFM only recognizes a table once the *delimiter*\n *     row (`| --- | --- |`) has arrived, so a freshly-streamed header line first\n *     renders as a plain paragraph and then snaps into a `<table>`.\n *  2. Partial-row flicker. The in-flight last row grows cell-by-cell.\n *\n * This module rewrites table-in-progress regions so a real `<table>` renders\n * from the first row onward with a stable column count: it completes the\n * delimiter row as soon as it starts streaming and pads the trailing partial\n * row to the header's column count. Combined with `table-layout: fixed` while\n * streaming (see `.persona-content-streaming table` in widget.css), columns lock\n * to even widths so rows append vertically without horizontal reflow.\n *\n * It runs ONLY while a message is streaming; the final render uses the real,\n * untouched `marked` output, so correctness is never affected.\n */\n\n/**\n * A GFM delimiter row, full or still streaming in: only delimiter characters\n * (`-`, `:`, `|`, whitespace) and at least one dash. Matches `|`-led partials\n * like `| -`, `|--`, `| :--`, and complete rows like `| --- | :--: |`.\n */\nconst DELIMITER_RE = /^\\s*\\|?[\\s:|-]*-[\\s:|-]*$/;\n\n/** A candidate table row contains at least one pipe. */\nconst hasPipe = (line: string): boolean => line.includes(\"|\");\n\n/** Split a markdown table row into trimmed cell strings, ignoring outer pipes. */\nconst splitCells = (line: string): string[] => {\n  let s = line.trim();\n  if (s.startsWith(\"|\")) s = s.slice(1);\n  if (s.endsWith(\"|\")) s = s.slice(0, -1);\n  return s.split(\"|\").map((cell) => cell.trim());\n};\n\n/** Render cells back into a normalized, pipe-delimited row. */\nconst buildRow = (cells: string[]): string => `| ${cells.join(\" | \")} |`;\n\n/** Build a complete delimiter row with the given column count. */\nconst buildDelimiter = (cols: number): string =>\n  `| ${Array.from({ length: cols }, () => \"---\").join(\" | \")} |`;\n\n/** Pad (or trim) a row's cells to exactly `cols` columns. */\nconst fitCells = (cells: string[], cols: number): string[] => {\n  if (cells.length >= cols) return cells.slice(0, cols);\n  return cells.concat(Array.from({ length: cols - cells.length }, () => \"\"));\n};\n\n/**\n * Normalize any streaming-in-progress GFM tables in `markdown` so they render as\n * complete tables with a stable column count. Returns the input unchanged when\n * there is nothing to stabilize (cheap fast-path for the common no-table case).\n */\nexport const stabilizeStreamingTables = (markdown: string): string => {\n  if (!markdown || !markdown.includes(\"|\")) return markdown;\n\n  const lines = markdown.split(\"\\n\");\n  let changed = false;\n\n  for (let i = 0; i < lines.length - 1; i++) {\n    const header = lines[i];\n    const delimiter = lines[i + 1];\n\n    // A table starts at a header line (has a pipe, is not itself a delimiter)\n    // immediately followed by a delimiter row that is full or still streaming.\n    if (!hasPipe(header) || DELIMITER_RE.test(header)) continue;\n    if (!DELIMITER_RE.test(delimiter)) continue;\n\n    const cols = splitCells(header).length;\n    if (cols < 1) continue;\n\n    // Complete the delimiter to match the header's column count so `marked`\n    // recognizes the table immediately instead of waiting for it to finish.\n    const fullDelimiter = buildDelimiter(cols);\n    if (lines[i + 1] !== fullDelimiter) {\n      lines[i + 1] = fullDelimiter;\n      changed = true;\n    }\n\n    // Normalize body rows (including a partial trailing one) to `cols` columns\n    // so each row occupies its slot instead of growing cell-by-cell. The region\n    // ends at the first blank or pipe-less line.\n    let j = i + 2;\n    for (; j < lines.length; j++) {\n      const row = lines[j];\n      if (row.trim() === \"\" || !hasPipe(row)) break;\n      const normalized = buildRow(fitCells(splitCells(row), cols));\n      if (lines[j] !== normalized) {\n        lines[j] = normalized;\n        changed = true;\n      }\n    }\n\n    i = j - 1; // resume scanning after this table region\n  }\n\n  return changed ? lines.join(\"\\n\") : markdown;\n};\n","/**\n * WebMCP consumption bridge.\n *\n * Owns the per-widget lifecycle of `@mcp-b/webmcp-polyfill`:\n *   - installs the polyfill (lazily, only when enabled) so `document.modelContext`\n *     is present;\n *   - snapshots the host page's tool registry per dispatch turn for\n *     `dispatch.clientTools[]`;\n *   - executes `webmcp:*` tool calls returned by the agent, mediating a single\n *     confirm-bubble gate before invoking the page's `execute()`.\n *\n * Spec reference: WebMCP (https://webmachinelearning.github.io/webmcp/).\n * Wire-level merging, namespace prefixing, and server-side allowlist\n * enforcement live on the Runtype API; this bridge mirrors those checks\n * client-side as a usability convenience, not a security boundary.\n *\n * About `@mcp-b/webmcp-polyfill`: it polyfills the *strict standard surface*\n * only (`registerTool` / `getTools` / `executeTool` on `document.modelContext`),\n * with no MCP-B-only extensions. The spec standardizes the *producer* side;\n * Persona is an in-page *consumer*, so it reads the registry via the\n * producer-facing preview API:\n *   - `getTools()`: async; returns `{ name, description, inputSchema }` where\n *     `inputSchema` is a JSON *string*. Annotations are not exposed here.\n *   - `executeTool(toolInfo, inputArgsJson, { signal })`: async; validates args\n *     against the tool's schema, runs `execute()`, and returns the raw result as\n *     a JSON *string* (or `null` for `undefined`). Honors `signal` for abort.\n *\n * The polyfill auto-installs `document.modelContext` at module-evaluation time,\n * so it is imported *dynamically* and only when `config.webmcp.enabled === true`\n *: a static import would install the global for every widget consumer,\n * including those that never opted into WebMCP.\n *\n * Confirm model: every `webmcp:*` call goes through one confirm gate before\n * `execute()` runs, regardless of `annotations.readOnlyHint`. (The polyfill owns\n * the spec's `client.requestUserInteraction` callback internally; Persona cannot\n * inject a nested confirm there, so the single outer gate is the whole story.)\n */\n\nimport type {\n  AgentWidgetWebMcpConfig,\n  ClientToolDefinition,\n  WebMcpConfirmHandler,\n  WebMcpConfirmInfo,\n  WebMcpToolResult,\n} from \"./types\";\n\n/**\n * Default per-call timeout for a WebMCP tool's `execute()`. Bounds how long\n * Persona waits before telling the agent the tool failed, keeping a misbehaving\n * tool from pinning the agent indefinitely. The timeout aborts the polyfill's\n * `executeTool` via an `AbortSignal`, so the page's work is asked to stop too\n * (cooperatively: a tool that ignores the signal may still complete).\n */\nconst DEFAULT_TOOL_TIMEOUT_MS = 30_000;\n\n/** Server-applied wire prefix; strip when looking up registry entries. */\nexport const WEBMCP_TOOL_PREFIX = \"webmcp:\";\n\n/**\n * Minimal structural view of the `@mcp-b/webmcp-polyfill` strict-core surface\n * that Persona consumes. We declare only what we use rather than depending on\n * `@mcp-b/webmcp-types` so the widget's type surface stays self-contained.\n */\ninterface ModelContextToolInfo {\n  name: string;\n  description: string;\n  /** JSON-encoded JSON Schema for the tool's input. */\n  inputSchema?: string;\n  /**\n   * Display title declared on the tool (`ToolDescriptor.title` in the WebMCP\n   * spec). The polyfill returns `\"\"` when the tool didn't declare one. Note:\n   * `annotations` (incl. the legacy `annotations.title`) are NOT exposed on\n   * this strict consumer surface: top-level `title` is the only display-name\n   * channel available to us.\n   */\n  title?: string;\n}\n\ninterface ModelContextCoreLike {\n  getTools(): Promise<ModelContextToolInfo[]>;\n  executeTool(\n    tool: ModelContextToolInfo,\n    inputArgsJson: string,\n    options?: { signal?: AbortSignal },\n  ): Promise<string | null>;\n}\n\n/**\n * Page-global map of bare tool name → declared display title\n * (`ToolDescriptor.title`). `document.modelContext` is page-global, so a\n * single map shared across widget/bridge instances is semantically correct.\n * Refreshed on every registry read (`snapshotForDispatch` / `executeToolCall`)\n * and consumed by the approval bubble's summary line via\n * `getWebMcpToolDisplayTitle`.\n */\nconst webMcpToolDisplayTitles = new Map<string, string>();\n\n/**\n * Record declared display titles from a fresh `getTools()` read. The map is\n * rebuilt from scratch, callers always pass the FULL registry snapshot, so\n * a tool that unregistered or dropped its title can't leave a stale label\n * behind. Exported for tests; production callers are the bridge's registry\n * reads.\n */\nexport const recordWebMcpToolDisplayTitles = (\n  infos: ModelContextToolInfo[],\n): void => {\n  webMcpToolDisplayTitles.clear();\n  for (const info of infos) {\n    const title = info.title?.trim();\n    if (title) webMcpToolDisplayTitles.set(info.name, title);\n  }\n};\n\n/**\n * Look up the display title a page tool declared via the WebMCP spec's\n * `ToolDescriptor.title`. Accepts wire (`webmcp:add_to_cart`) or bare\n * (`add_to_cart`) names. Returns `undefined` when the tool didn't declare\n * one (callers fall back to humanizing the tool name).\n */\nexport const getWebMcpToolDisplayTitle = (\n  toolName: string,\n): string | undefined => webMcpToolDisplayTitles.get(stripWebMcpPrefix(toolName));\n\nconst log = {\n  warn(message: string, ...rest: unknown[]): void {\n    if (typeof console !== \"undefined\" && typeof console.warn === \"function\") {\n      // eslint-disable-next-line no-console\n      console.warn(`[Persona/WebMCP] ${message}`, ...rest);\n    }\n  },\n};\n\n/** The slice of `@mcp-b/webmcp-polyfill` the bridge consumes on install. */\nexport type WebMcpPolyfillModule = {\n  initializeWebMCPPolyfill: () => void;\n};\n\n/**\n * Override how the polyfill module is obtained. By default the bridge does\n * `import(\"@mcp-b/webmcp-polyfill\")`, which bundlers resolve for npm\n * consumers. The IIFE/CDN build can't resolve a bare specifier at runtime, so\n * its entry (`index-global.ts`) registers a loader that imports the\n * self-contained `webmcp-polyfill.js` chunk from a URL derived from the\n * widget script's own `src`. Page-global, like `document.modelContext`\n * itself. Pass `null` to restore the default (used by tests).\n */\nlet polyfillLoader: (() => Promise<WebMcpPolyfillModule>) | null = null;\n\nexport const setWebMcpPolyfillLoader = (\n  loader: (() => Promise<WebMcpPolyfillModule>) | null,\n): void => {\n  polyfillLoader = loader;\n};\n\n/**\n * Compute a stable, order-independent fingerprint of a `ClientToolDefinition[]`\n * snapshot, for the diff-only / send-once dispatch path (client-token mode).\n *\n * The widget caches \"the fingerprint of the tool set last sent in full\" for the\n * current session; an unchanged set on a follow-up turn lets it ship only the\n * fingerprint instead of the whole array. Per-tool strings are sorted so tool\n * ordering does not affect the result. `pageOrigin` is deliberately excluded: * it is audit metadata, not part of the tool contract.\n *\n * This is a fast, non-cryptographic content key. The canonical per-tool content\n * is hashed down to a short, fixed-length digest so the result fits the server's\n * `clientToolsFingerprint` wire field (`z.string().max(128)`) regardless of how\n * many tools the page registers: sending the raw concatenated content would\n * overflow that bound and be rejected with a 400. The server stores and compares\n * the widget's fingerprint verbatim, so cross-implementation byte-equality is NOT\n * required: only self-consistency across this widget's turns.\n */\nexport function computeClientToolsFingerprint(\n  tools: ClientToolDefinition[],\n): string {\n  if (tools.length === 0) return \"0:empty\";\n  const parts = tools\n    .map((t) =>\n      [\n        t.name,\n        t.description ?? \"\",\n        t.parametersSchema ? JSON.stringify(t.parametersSchema) : \"\",\n        t.origin ?? \"\",\n        t.annotations ? JSON.stringify(t.annotations) : \"\",\n      ].join(\"\\x1f\"),\n    )\n    .sort();\n  return `${tools.length}:${hashFingerprintContent(parts.join(\"\\x1e\"))}`;\n}\n\n/**\n * cyrb53: a fast, well-distributed non-cryptographic string hash. Returns a\n * 53-bit value (safe-integer range). Two independent seeds are combined by the\n * caller for a ~106-bit digest, which makes accidental collisions across a\n * single conversation's handful of tool-set variants infeasible.\n */\nfunction cyrb53(str: string, seed: number): number {\n  let h1 = 0xdeadbeef ^ seed;\n  let h2 = 0x41c6ce57 ^ seed;\n  for (let i = 0; i < str.length; i++) {\n    const ch = str.charCodeAt(i);\n    h1 = Math.imul(h1 ^ ch, 2654435761);\n    h2 = Math.imul(h2 ^ ch, 1597334677);\n  }\n  h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n  h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n  h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n  return 4294967296 * (2097151 & h2) + (h1 >>> 0);\n}\n\n/**\n * Compress the canonical tool-set content string into a short, fixed-length\n * fingerprint (≤ ~24 chars) that fits the server's 128-char wire bound. Uses two\n * seeded cyrb53 passes, base-36 encoded.\n */\nfunction hashFingerprintContent(content: string): string {\n  const a = cyrb53(content, 0).toString(36);\n  const b = cyrb53(content, 0x9e3779b1).toString(36);\n  return `${a}.${b}`;\n}\n\nexport class WebMcpBridge {\n  private confirmHandler: WebMcpConfirmHandler | null;\n  private readonly timeoutMs: number;\n\n  /** `true` once the polyfill has been (idempotently) installed. */\n  private installed = false;\n  /** Memoizes the one-shot async install so concurrent callers share it. */\n  private readyPromise: Promise<void> | null = null;\n  /**\n   * Warn-once latch for a present-but-incompatible `document.modelContext`\n   * (some other / older WebMCP polyfill squatting the global). `getModelContext`\n   * is hit on every snapshot + execute, so we log the diagnostic only once.\n   */\n  private incompatibleContextWarned = false;\n\n  constructor(private readonly config: AgentWidgetWebMcpConfig) {\n    this.confirmHandler = config.onConfirm ?? null;\n    this.timeoutMs = DEFAULT_TOOL_TIMEOUT_MS;\n  }\n\n  /**\n   * Override the confirm handler post-construction. Used by `ui.ts` to wire\n   * the in-panel approval bubble after the client has been built (the widget\n   * lifecycle constructs the client before the panel renders).\n   */\n  public setConfirmHandler(handler: WebMcpConfirmHandler | null): void {\n    this.confirmHandler = handler;\n  }\n\n  /**\n   * `true` when the bridge can both snapshot the registry AND execute returned\n   * tool calls: i.e. the polyfill is installed and `document.modelContext`\n   * exposes the consumer surface (`getTools` / `executeTool`). Native browsers\n   * that ship `document.modelContext` satisfy this too.\n   *\n   * Synchronous and best-effort: returns `false` until the lazy install has\n   * resolved (see `ensureReady`). The snapshot/execute paths await readiness\n   * themselves, so this is purely an advisory check for callers.\n   */\n  public isOperational(): boolean {\n    if (this.config.enabled !== true) return false;\n    if (!this.installed) return false;\n    return this.getModelContext() !== null;\n  }\n\n  /**\n   * Per-turn snapshot for `dispatch.clientTools[]`. Returns the JSON-only\n   * surface: `execute` stays client-side, reached later via `executeToolCall`.\n   *\n   * Async because the strict polyfill's `getTools()` is async. Both payload\n   * builders in `client.ts` already `await`, so this adds no new ceremony.\n   */\n  public async snapshotForDispatch(): Promise<ClientToolDefinition[]> {\n    await this.ensureReady();\n    if (this.config.enabled !== true) return [];\n\n    const mc = this.getModelContext();\n    if (!mc) return [];\n\n    let infos: ModelContextToolInfo[];\n    try {\n      infos = await mc.getTools();\n    } catch (err) {\n      log.warn(\"getTools() threw: shipping an empty WebMCP snapshot.\", err);\n      return [];\n    }\n    recordWebMcpToolDisplayTitles(infos);\n\n    const pageOrigin = typeof location !== \"undefined\" ? location.origin : \"\";\n\n    return infos\n      .filter((info) => this.passesClientAllowlist(info.name))\n      .map<ClientToolDefinition>((info) => {\n        const def: ClientToolDefinition = {\n          name: info.name,\n          description: info.description,\n          origin: \"webmcp\",\n          ...(pageOrigin ? { pageOrigin } : {}),\n        };\n        const schema = parseSchema(info.inputSchema);\n        if (schema) def.parametersSchema = schema;\n        return def;\n      });\n  }\n\n  /**\n   * Execute a `webmcp:<name>` tool call returned by the agent and return the\n   * normalized MCP-shaped result for `/resume`.\n   *\n   * Failure modes: all return `{ isError: true, content: [...] }` rather than\n   * throwing, so the dispatch can resume cleanly:\n   *   - bridge not operational\n   *   - tool not in registry (e.g. unmounted between snapshot and call)\n   *   - tool excluded by the client allowlist\n   *   - user declined the confirm gate\n   *   - `execute()` threw or failed schema validation\n   *   - `execute()` exceeded the 30s timeout\n   *   - `signal` fired (session-level `cancel()`)\n   *\n   * When `signal` is provided, abort is honored at three points: before the\n   * confirm bubble renders, after the user approves but before `execute()`\n   * runs, and (via a combined `AbortController`) during `execute()` itself.\n   * Honoring abort BEFORE the confirm prevents a late approval after `cancel()`\n   * from firing a host-page side effect with no matching `/resume`.\n   */\n  public async executeToolCall(\n    wireToolName: string,\n    args: unknown,\n    signal?: AbortSignal,\n  ): Promise<WebMcpToolResult> {\n    await this.ensureReady();\n    if (this.config.enabled !== true) {\n      return errorResult(\n        \"WebMCP is not enabled on this widget.\",\n      );\n    }\n\n    const mc = this.getModelContext();\n    if (!mc) {\n      // Distinguish \"no modelContext at all\" from \"present but incompatible\"\n      // (a foreign/older polyfill squatting document.modelContext) so the\n      // resumed error is actionable. getModelContext has already warned once\n      // for the incompatible case.\n      const present =\n        typeof document !== \"undefined\" &&\n        Boolean((document as Document & { modelContext?: unknown }).modelContext);\n      return errorResult(\n        present\n          ? \"WebMCP is not operational: document.modelContext is present but does not expose the strict getTools()/executeTool() surface (likely a different or older WebMCP polyfill).\"\n          : \"WebMCP bridge is not operational on this page (document.modelContext not available).\",\n      );\n    }\n\n    const bareName = stripWebMcpPrefix(wireToolName);\n\n    let infos: ModelContextToolInfo[];\n    try {\n      infos = await mc.getTools();\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      return errorResult(`Failed to read WebMCP registry: ${message}`);\n    }\n    recordWebMcpToolDisplayTitles(infos);\n    const info = infos.find((candidate) => candidate.name === bareName);\n\n    if (!info) {\n      return errorResult(\n        `WebMCP tool not registered on this page: ${bareName}`,\n      );\n    }\n\n    // Re-apply the client-side allowlist at execute time. `snapshotForDispatch`\n    // already filters it for `clientTools[]`, but the agent could request a\n    // tool that the integrator excluded: e.g. a `webmcp:` call replayed from\n    // history, a server bug, or a page that re-registered a previously-hidden\n    // tool. The server is the trust boundary; this is a defense-in-depth\n    // convenience check to keep us symmetric with the snapshot.\n    if (!this.passesClientAllowlist(bareName)) {\n      return errorResult(\n        `WebMCP tool not allowed by client allowlist: ${bareName}`,\n      );\n    }\n\n    // Bail before the confirm renders: a late approval after cancel() would\n    // otherwise fire a host-page side effect with no matching /resume.\n    if (signal?.aborted) {\n      return errorResult(\"Aborted by cancel()\");\n    }\n\n    // Confirm-by-default gate. Every `webmcp:*` call routes through here,\n    // regardless of `annotations.readOnlyHint`.\n    const displayTitle = getWebMcpToolDisplayTitle(bareName);\n    const gateInfo: WebMcpConfirmInfo = {\n      toolName: bareName,\n      args,\n      description: info.description,\n      ...(displayTitle ? { title: displayTitle } : {}),\n      reason: \"gate\",\n    };\n    if (!(await this.requestConfirm(gateInfo))) {\n      return errorResult(\"User declined the tool call.\");\n    }\n\n    // The await above may have parked us long enough for cancel() to fire.\n    // Bail before invoking `execute()` so we don't fire a side effect that\n    // the server can no longer accept a `/resume` for.\n    if (signal?.aborted) {\n      return errorResult(\"Aborted by cancel()\");\n    }\n\n    // Drive both the 30s timeout and the caller's `signal` through a single\n    // AbortController passed to `executeTool`. The polyfill races the page's\n    // `execute()` against this signal, so abort is cooperative: a tool that\n    // ignores the signal may still complete on the page after the agent gets\n    // an `isError` result. Side-effectful tools should bound their own work.\n    const controller = new AbortController();\n    let timedOut = false;\n    const timer = setTimeout(() => {\n      timedOut = true;\n      controller.abort();\n    }, this.timeoutMs);\n    const onAbort = () => controller.abort();\n    if (signal) {\n      if (signal.aborted) controller.abort();\n      else signal.addEventListener(\"abort\", onAbort, { once: true });\n    }\n\n    try {\n      const raw = await mc.executeTool(info, safeStringifyArgs(args), {\n        signal: controller.signal,\n      });\n      return normalizeSerializedResult(raw);\n    } catch (err) {\n      if (timedOut) {\n        return errorResult(\n          `WebMCP tool '${bareName}' timed out after ${this.timeoutMs}ms`,\n        );\n      }\n      if (signal?.aborted) {\n        return errorResult(\"Aborted by cancel()\");\n      }\n      const message = err instanceof Error ? err.message : String(err);\n      return errorResult(message);\n    } finally {\n      clearTimeout(timer);\n      if (signal) signal.removeEventListener(\"abort\", onAbort);\n    }\n  }\n\n  /**\n   * Lazily install `@mcp-b/webmcp-polyfill` the first time the bridge needs the\n   * registry. Idempotent and memoized. Dynamic import keeps the polyfill out of\n   * the main bundle and prevents it from installing `document.modelContext` for\n   * widget consumers that never enable WebMCP.\n   *\n   * Producer pages should still install the polyfill themselves (or import it)\n   * before registering tools: Persona's install is a fallback, and a page that\n   * registers tools at load before Persona's first dispatch needs the global to\n   * already exist.\n   */\n  private ensureReady(): Promise<void> {\n    if (this.config.enabled !== true) return Promise.resolve();\n    if (!this.readyPromise) {\n      this.readyPromise = this.install();\n    }\n    return this.readyPromise;\n  }\n\n  private async install(): Promise<void> {\n    try {\n      // A compatible registry is already on the page (the host installed the\n      // polyfill, or a native impl): initialize would no-op against it, so\n      // skip loading the module entirely. Pages that register tools before\n      // Persona's first dispatch always land here, because registering\n      // requires `document.modelContext` to exist.\n      if (this.getModelContext()) {\n        this.installed = true;\n        return;\n      }\n      const mod = polyfillLoader\n        ? await polyfillLoader()\n        : await import(\"@mcp-b/webmcp-polyfill\");\n      // Idempotent: no-ops if `document.modelContext` already exists (native or\n      // a prior install by the host page).\n      mod.initializeWebMCPPolyfill();\n      this.installed = true;\n    } catch (err) {\n      log.warn(\n        \"Failed to load @mcp-b/webmcp-polyfill: WebMCP consumption disabled.\",\n        err,\n      );\n      this.installed = false;\n    }\n  }\n\n  /**\n   * Read the consumer surface off `document.modelContext`, returning `null`\n   * when it is absent or doesn't expose the producer-preview API we rely on.\n   */\n  private getModelContext(): ModelContextCoreLike | null {\n    if (typeof document === \"undefined\") return null;\n    const mc = (document as Document & { modelContext?: unknown }).modelContext;\n    if (!mc || typeof mc !== \"object\") {\n      // Absent (not yet installed, or no WebMCP on this page): not an error,\n      // and not worth warning about; the snapshot/execute paths fall back to a\n      // clean \"not operational\" result.\n      return null;\n    }\n    const core = mc as Partial<ModelContextCoreLike>;\n    if (\n      typeof core.getTools !== \"function\" ||\n      typeof core.executeTool !== \"function\"\n    ) {\n      // A `document.modelContext` IS present but doesn't expose the strict-core\n      // surface we consume (`getTools` / `executeTool`). This usually means a\n      // different or older WebMCP polyfill (or a native impl on a divergent\n      // draft) installed the global first: which `@mcp-b/webmcp-polyfill`\n      // correctly declines to overwrite. Warn once so integrators understand\n      // why WebMCP is inert instead of seeing a silent no-op.\n      if (!this.incompatibleContextWarned) {\n        this.incompatibleContextWarned = true;\n        log.warn(\n          \"document.modelContext is present but does not expose getTools()/executeTool(): \" +\n            \"WebMCP consumption is disabled. Another (incompatible or older) WebMCP polyfill \" +\n            \"likely installed document.modelContext before Persona. Remove it, or use a polyfill \" +\n            \"implementing the strict standard surface (e.g. @mcp-b/webmcp-polyfill).\",\n        );\n      }\n      return null;\n    }\n    return mc as ModelContextCoreLike;\n  }\n\n  private async requestConfirm(info: WebMcpConfirmInfo): Promise<boolean> {\n    const handler = this.confirmHandler ?? defaultBrowserConfirmHandler;\n    try {\n      return await handler(info);\n    } catch (err) {\n      log.warn(\n        `Confirm handler threw for WebMCP tool '${info.toolName}'; declining.`,\n        err,\n      );\n      return false;\n    }\n  }\n\n  private passesClientAllowlist(toolName: string): boolean {\n    const list = this.config.allowlist;\n    if (!list || list.length === 0) return true;\n    return list.some((pattern) => matchesGlob(toolName, pattern));\n  }\n}\n\n/**\n * Strip the server-applied `webmcp:` prefix from a wire-format tool name.\n * Exported for tests; widget code should always go through the bridge.\n */\nexport const stripWebMcpPrefix = (name: string): string =>\n  name.startsWith(WEBMCP_TOOL_PREFIX)\n    ? name.slice(WEBMCP_TOOL_PREFIX.length)\n    : name;\n\n/**\n * `true` when `wireToolName` carries the `webmcp:` prefix. Used by `client.ts`\n * to route `step_await` events.\n */\nexport const isWebMcpToolName = (name: string): boolean =>\n  name.startsWith(WEBMCP_TOOL_PREFIX);\n\n/**\n * Glob match with `*` as the only wildcard. Matches any sequence of any\n * characters. Sufficient for the spec's prefix-style allowlists like\n * `search_*` or `list_*`. Tool names themselves cannot contain `:`\n * (see polyfill validation), so we don't need to special-case it.\n */\nconst matchesGlob = (name: string, pattern: string): boolean => {\n  if (pattern === \"*\") return true;\n  // Escape regex metachars except `*`, then convert `*` to `.*`.\n  const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n  const regex = new RegExp(\"^\" + escaped.replace(/\\*/g, \".*\") + \"$\");\n  return regex.test(name);\n};\n\n/**\n * Parse the JSON-string `inputSchema` from `getTools()` back into an object for\n * `parametersSchema`. Returns `undefined` for a missing or unparseable schema\n * (the server can still accept a tool with no declared parameters).\n */\nconst parseSchema = (raw: string | undefined): object | undefined => {\n  if (raw === undefined || raw === \"\") return undefined;\n  try {\n    const parsed = JSON.parse(raw);\n    return parsed !== null && typeof parsed === \"object\"\n      ? (parsed as object)\n      : undefined;\n  } catch {\n    return undefined;\n  }\n};\n\n/**\n * Normalize the JSON-string result from `executeTool` into MCP `CallToolResult`\n * shape. The polyfill returns `JSON.stringify(rawResult)` (the tool's raw\n * `execute()` return, NOT pre-normalized) or `null` for an `undefined` return.\n * Already-shaped returns (with `content: [...]`) pass through; everything else\n * becomes a single text block. Tools that intentionally return MCP errors\n * should set `isError: true` themselves.\n */\nconst normalizeSerializedResult = (raw: string | null): WebMcpToolResult => {\n  if (raw === null || raw === undefined) {\n    return { content: [{ type: \"text\", text: \"\" }] };\n  }\n\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(raw);\n  } catch {\n    // Not valid JSON (shouldn't happen, the polyfill stringifies), surface\n    // the raw string as text rather than dropping it.\n    return { content: [{ type: \"text\", text: raw }] };\n  }\n\n  if (\n    parsed !== null &&\n    typeof parsed === \"object\" &&\n    Array.isArray((parsed as { content?: unknown }).content)\n  ) {\n    return parsed as WebMcpToolResult;\n  }\n\n  const text = typeof parsed === \"string\" ? parsed : safeStringify(parsed);\n  return { content: [{ type: \"text\", text }] };\n};\n\nconst errorResult = (message: string): WebMcpToolResult => ({\n  isError: true,\n  content: [{ type: \"text\", text: message }],\n});\n\n/**\n * Fallback confirm UI: `window.confirm()`. Production deployments should wire\n * `config.webmcp.onConfirm` to a handler matched to their UX (e.g. an inline\n * approval bubble). Declines silently in non-browser environments (SSR, tests\n * without a DOM).\n */\nconst defaultBrowserConfirmHandler: WebMcpConfirmHandler = async (info) => {\n  if (typeof window === \"undefined\" || typeof window.confirm !== \"function\") {\n    return false;\n  }\n  const argsPreview = previewArgs(info.args);\n  const prompt =\n    `Allow the AI to call ${info.toolName}` +\n    (argsPreview ? `\\n\\nArguments:\\n${argsPreview}` : \"\") +\n    (info.description ? `\\n\\n${info.description}` : \"\");\n  return window.confirm(prompt);\n};\n\nconst previewArgs = (args: unknown): string => {\n  if (args === undefined || args === null) return \"\";\n  try {\n    const json = JSON.stringify(args, null, 2);\n    return json.length > 500 ? json.slice(0, 500) + \"…\" : json;\n  } catch {\n    return String(args);\n  }\n};\n\n/**\n * Stringify tool args for `executeTool(toolInfo, inputArgsJson)`. Falls back to\n * `{}` for `undefined`/non-serializable args so the polyfill always receives a\n * valid JSON object string to validate against the tool schema.\n */\nconst safeStringifyArgs = (args: unknown): string => {\n  if (args === undefined) return \"{}\";\n  try {\n    const json = JSON.stringify(args);\n    return json === undefined ? \"{}\" : json;\n  } catch {\n    return \"{}\";\n  }\n};\n\n/**\n * `JSON.stringify` that tolerates circular references and non-serializable\n * values. A misbehaving tool result shouldn't break the resume path.\n */\nconst safeStringify = (value: unknown): string => {\n  if (value === undefined) return \"\";\n  try {\n    return JSON.stringify(value);\n  } catch {\n    return String(value);\n  }\n};\n","import type {\n  AgentWidgetArtifactsFeature,\n  PersonaArtifactDisplayMode,\n  PersonaArtifactFileMeta\n} from \"../types\";\n\n/**\n * Resolves the display mode for an artifact of the given type from\n * `features.artifacts.display`.\n *\n * Resolution order: `display.byType[artifactType]`, else `display.default`,\n * else `display` (string form), else `\"panel\"` (current behavior).\n */\nexport function resolveArtifactDisplayMode(\n  feature: AgentWidgetArtifactsFeature | undefined,\n  artifactType: string\n): PersonaArtifactDisplayMode {\n  const display = feature?.display;\n  if (!display) return \"panel\";\n  if (typeof display === \"string\") return display;\n  const byType = display.byType as\n    | Partial<Record<string, PersonaArtifactDisplayMode>>\n    | undefined;\n  return byType?.[artifactType] ?? display.default ?? \"panel\";\n}\n\n/** Props embedded in an in-thread artifact block's rawContent JSON. */\nexport type ArtifactRefBlockProps = {\n  artifactId: string;\n  title?: string;\n  artifactType: string;\n  status: \"streaming\" | \"complete\";\n  /** File metadata for previewable file artifacts (persists the download path). */\n  file?: PersonaArtifactFileMeta;\n  /**\n   * Component name for component-type artifacts. Only embedded for the\n   * \"inline\" display mode: the inline block renders component artifacts\n   * through the registry, so it needs the name in its props; the card never\n   * reads it.\n   */\n  component?: string;\n  /**\n   * Props for component-type artifacts. Embedded alongside `component` for the\n   * \"inline\" display mode so a refreshed session re-invokes the registered\n   * renderer with its real props (the session artifact registry is not\n   * persisted); the card never reads them.\n   */\n  componentProps?: Record<string, unknown>;\n  /** Final markdown content, embedded once complete so hydration/download work after refresh. */\n  markdown?: string;\n};\n\n/**\n * Builds the `rawContent` JSON for the synthetic assistant message that\n * anchors an artifact in the transcript (message id `artifact-ref-<id>`).\n *\n * \"card\"/\"panel\" modes render the compact `PersonaArtifactCard`; \"inline\"\n * renders the `PersonaArtifactInline` preview block. Both share this shape so\n * transcript persistence and hydration work unchanged. Used by the streaming\n * client (`artifact_start`) and the programmatic `session.upsertArtifact()`.\n */\nexport function buildArtifactRefRawContent(\n  displayMode: PersonaArtifactDisplayMode,\n  props: ArtifactRefBlockProps\n): string {\n  return JSON.stringify({\n    component:\n      displayMode === \"inline\" ? \"PersonaArtifactInline\" : \"PersonaArtifactCard\",\n    props: {\n      artifactId: props.artifactId,\n      title: props.title,\n      artifactType: props.artifactType,\n      status: props.status,\n      ...(props.file ? { file: props.file } : {}),\n      ...(displayMode === \"inline\" && props.component\n        ? { component: props.component }\n        : {}),\n      ...(displayMode === \"inline\" && props.componentProps\n        ? { componentProps: props.componentProps }\n        : {}),\n      ...(props.markdown !== undefined ? { markdown: props.markdown } : {})\n    }\n  });\n}\n","/**\n * Target resolution for the normalized, backend-neutral `target` field.\n *\n * `target` is a single string that selects which backend resource a widget\n * talks to, optimized for a browser widget (always serializable, no live\n * objects). Three shapes are supported:\n *\n *   - Runtype TypeID (no prefix): `\"agent_…\"` / `\"flow_…\"` route to the\n *     Runtype agent/flow paths. The TypeID prefix is self-describing, so no\n *     wrapper is needed for the common case.\n *   - Provider-prefixed: `\"<provider>:<id>\"` is handed to the matching\n *     `targetProviders[provider]` resolver, which returns the dispatch payload\n *     fragment for that backend (e.g. `eve`, `langgraph`). `\"runtype:…\"` is a\n *     built-in that re-detects a TypeID.\n *   - Bare name: `\"support\"` requires a `targetProviders.default` resolver,\n *     otherwise it throws (a bare name is ambiguous without one).\n *\n * Resolvers are registered, not passed as the value, which keeps `target`\n * itself a plain string that survives script-tag installs, `data-config`,\n * persisted state, and codegen.\n */\n\n/** Resolver for a provider-prefixed (or default) target id. */\nexport type TargetResolver = (id: string) => { payload: Record<string, unknown> };\n\n/** Normalized routing produced from a `target` string. */\nexport type ResolvedTarget =\n  | { kind: \"agentId\"; agentId: string }\n  | { kind: \"flowId\"; flowId: string }\n  | { kind: \"payload\"; payload: Record<string, unknown> };\n\nconst RUNTYPE_AGENT_PREFIX = \"agent_\";\nconst RUNTYPE_FLOW_PREFIX = \"flow_\";\n\nfunction detectRuntypeTypeId(id: string): ResolvedTarget | null {\n  if (id.startsWith(RUNTYPE_AGENT_PREFIX)) return { kind: \"agentId\", agentId: id };\n  if (id.startsWith(RUNTYPE_FLOW_PREFIX)) return { kind: \"flowId\", flowId: id };\n  return null;\n}\n\n/**\n * Resolve a `target` string into normalized routing. Pure and synchronous so\n * it can run on the dispatch hot path and be unit-tested in isolation.\n */\nexport function resolveTarget(\n  target: string,\n  targetProviders?: Record<string, TargetResolver>,\n): ResolvedTarget {\n  const trimmed = target.trim();\n  if (!trimmed) {\n    throw new Error(\"[Persona] `target` is empty.\");\n  }\n\n  const colon = trimmed.indexOf(\":\");\n  if (colon > 0) {\n    const prefix = trimmed.slice(0, colon);\n    const rest = trimmed.slice(colon + 1);\n\n    // Built-in: an explicit `runtype:` prefix wrapping a TypeID.\n    if (prefix === \"runtype\") {\n      const detected = detectRuntypeTypeId(rest);\n      if (detected) return detected;\n      throw new Error(\n        `[Persona] target \"runtype:${rest}\" is not a valid Runtype agent_/flow_ id.`,\n      );\n    }\n\n    const resolver = targetProviders?.[prefix];\n    if (!resolver) {\n      throw new Error(\n        `[Persona] No target provider registered for \"${prefix}\". ` +\n          `Add a \\`targetProviders.${prefix}\\` resolver, or use a Runtype agent_/flow_ id.`,\n      );\n    }\n    return { kind: \"payload\", payload: resolver(rest).payload };\n  }\n\n  // No prefix: a bare Runtype TypeID is self-describing.\n  const detected = detectRuntypeTypeId(trimmed);\n  if (detected) return detected;\n\n  // Bare, non-TypeID name: only resolvable via an explicit default resolver.\n  const fallback = targetProviders?.default;\n  if (fallback) return { kind: \"payload\", payload: fallback(trimmed).payload };\n\n  throw new Error(\n    `[Persona] target \"${trimmed}\" has no provider prefix and is not a Runtype agent_/flow_ id. ` +\n      `Use \"<provider>:${trimmed}\", a Runtype TypeID, or register a \\`targetProviders.default\\` resolver.`,\n  );\n}\n","import { parse as parsePartialJson, ARR, OBJ, STR } from \"partial-json\";\nimport { createElement } from \"../utils/dom\";\nimport {\n  AgentWidgetAskUserQuestionFeature,\n  AgentWidgetConfig,\n  AgentWidgetMessage,\n  AskUserQuestionOption,\n  AskUserQuestionPayload,\n  AskUserQuestionPrompt,\n} from \"../types\";\n\nexport const ASK_USER_QUESTION_TOOL_NAME = \"ask_user_question\";\nexport const ASK_USER_QUESTION_MAX = 8;\n\nconst SHEET_SENTINEL = \"data-persona-ask-sheet-for\";\nconst DEFAULT_FREE_TEXT_LABEL_ROWS = \"Other\";\nconst DEFAULT_FREE_TEXT_LABEL_PILLS = \"Other…\";\nconst DEFAULT_FREE_TEXT_PLACEHOLDER = \"Type your own answer here\";\nconst DEFAULT_SUBMIT_LABEL = \"Send\";\nconst DEFAULT_NEXT_LABEL = \"Next\";\nconst DEFAULT_BACK_LABEL = \"Back\";\nconst DEFAULT_SUBMIT_ALL_LABEL = \"Submit all\";\nconst DEFAULT_SKIP_LABEL = \"Skip\";\nconst DEFAULT_SKELETON_PILLS = 3;\n\nexport const ATTR_CURRENT_INDEX = \"data-ask-current-index\";\nexport const ATTR_QUESTION_COUNT = \"data-ask-question-count\";\nexport const ATTR_ANSWERS = \"data-ask-answers\";\nexport const ATTR_GROUPED = \"data-ask-grouped\";\nexport const ATTR_LAYOUT = \"data-ask-layout\";\n\nexport type AskUserQuestionLayout = \"rows\" | \"pills\";\n\nexport const resolveLayout = (\n  feature: AgentWidgetAskUserQuestionFeature\n): AskUserQuestionLayout => (feature.layout === \"pills\" ? \"pills\" : \"rows\");\n\nexport const getLayout = (sheet: HTMLElement): AskUserQuestionLayout =>\n  sheet.getAttribute(ATTR_LAYOUT) === \"pills\" ? \"pills\" : \"rows\";\n\nlet truncateWarned = false;\n\n/**\n * Escape a tool-call id for safe use inside a CSS attribute selector.\n * `CSS.escape` would work but isn't available in all test environments (jsdom).\n */\nconst escapeAttrValue = (value: string): string => value.replace(/[\"\\\\]/g, \"\\\\$&\");\n\nexport const isAskUserQuestionMessage = (message: AgentWidgetMessage): boolean => {\n  return (\n    message.variant === \"tool\" &&\n    !!message.toolCall &&\n    message.toolCall.name === ASK_USER_QUESTION_TOOL_NAME\n  );\n};\n\nconst resolveFeature = (config?: AgentWidgetConfig): AgentWidgetAskUserQuestionFeature => {\n  return config?.features?.askUserQuestion ?? {};\n};\n\n/**\n * Parse an `ask_user_question` tool-variant message into a partial payload.\n * Safe to call mid-stream: will walk the tool call's `chunks` via\n * `partial-json` and return `{ payload: null, complete: false }` when there\n * isn't enough data yet. `complete` flips to `true` once the tool call\n * reports status `\"complete\"`.\n *\n * Exported for plugin authors implementing `renderAskUserQuestion`.\n */\nexport const parseAskUserQuestionPayload = (\n  message: AgentWidgetMessage\n): { payload: Partial<AskUserQuestionPayload> | null; complete: boolean } => {\n  const toolCall = message.toolCall;\n  if (!toolCall) return { payload: null, complete: false };\n\n  const complete = toolCall.status === \"complete\";\n\n  if (toolCall.args && typeof toolCall.args === \"object\") {\n    return { payload: toolCall.args as Partial<AskUserQuestionPayload>, complete };\n  }\n\n  const chunks = toolCall.chunks;\n  if (!chunks || chunks.length === 0) return { payload: null, complete };\n\n  try {\n    const text = chunks.join(\"\");\n    const parsed = parsePartialJson(text, STR | OBJ | ARR);\n    if (parsed && typeof parsed === \"object\") {\n      return { payload: parsed as Partial<AskUserQuestionPayload>, complete };\n    }\n  } catch {\n    // malformed; fall through\n  }\n  return { payload: null, complete };\n};\n\n/**\n * Return the questions array (capped to {@link ASK_USER_QUESTION_MAX}). Logs a\n * single one-shot warning if a payload exceeds the cap.\n */\nexport const promptsFromPayload = (\n  payload: Partial<AskUserQuestionPayload> | null\n): Partial<AskUserQuestionPrompt>[] => {\n  const all = Array.isArray(payload?.questions) ? (payload!.questions as Partial<AskUserQuestionPrompt>[]) : [];\n  if (all.length > ASK_USER_QUESTION_MAX && !truncateWarned) {\n    truncateWarned = true;\n    if (typeof console !== \"undefined\") {\n      // eslint-disable-next-line no-console\n      console.warn(\n        `[AgentWidget] ask_user_question received ${all.length} questions; truncating to ${ASK_USER_QUESTION_MAX}.`\n      );\n    }\n  }\n  return all.slice(0, ASK_USER_QUESTION_MAX);\n};\n\n/**\n * Kept for plugin authors who only want to render the first question.\n * @deprecated Plugins should iterate `payload.questions` themselves; the\n * built-in renderer now paginates multi-question payloads.\n */\nconst firstPrompt = (\n  payload: Partial<AskUserQuestionPayload> | null\n): Partial<AskUserQuestionPrompt> | null => {\n  return promptsFromPayload(payload)[0] ?? null;\n};\n\nconst promptAt = (\n  payload: Partial<AskUserQuestionPayload> | null,\n  index: number\n): Partial<AskUserQuestionPrompt> | null => {\n  return promptsFromPayload(payload)[index] ?? null;\n};\n\nconst applyStyleVars = (\n  root: HTMLElement,\n  feature: AgentWidgetAskUserQuestionFeature\n): void => {\n  const s = feature.styles;\n  if (!s) return;\n  if (s.sheetBackground) root.style.setProperty(\"--persona-ask-sheet-bg\", s.sheetBackground);\n  if (s.sheetBorder) root.style.setProperty(\"--persona-ask-sheet-border\", s.sheetBorder);\n  if (s.sheetShadow) root.style.setProperty(\"--persona-ask-sheet-shadow\", s.sheetShadow);\n  if (s.pillBackground) root.style.setProperty(\"--persona-ask-pill-bg\", s.pillBackground);\n  if (s.pillBackgroundSelected)\n    root.style.setProperty(\"--persona-ask-pill-bg-selected\", s.pillBackgroundSelected);\n  if (s.pillTextColor) root.style.setProperty(\"--persona-ask-pill-fg\", s.pillTextColor);\n  if (s.pillTextColorSelected)\n    root.style.setProperty(\"--persona-ask-pill-fg-selected\", s.pillTextColorSelected);\n  if (s.pillBorderRadius) root.style.setProperty(\"--persona-ask-pill-radius\", s.pillBorderRadius);\n  if (s.customInputBackground)\n    root.style.setProperty(\"--persona-ask-input-bg\", s.customInputBackground);\n};\n\nconst buildAffordance = (\n  layout: AskUserQuestionLayout,\n  multiSelect: boolean,\n  index: number\n): HTMLElement | null => {\n  if (layout !== \"rows\") return null;\n  const wrap = createElement(\"span\", \"persona-ask-row-affordance\");\n  wrap.setAttribute(\"aria-hidden\", \"true\");\n  if (multiSelect) {\n    const check = createElement(\"span\", \"persona-ask-row-check\");\n    wrap.appendChild(check);\n  } else {\n    const badge = createElement(\"span\", \"persona-ask-row-badge\");\n    badge.textContent = String(index + 1);\n    wrap.appendChild(badge);\n  }\n  return wrap;\n};\n\nconst buildPill = (\n  option: AskUserQuestionOption,\n  index: number,\n  layout: AskUserQuestionLayout,\n  multiSelect: boolean\n): HTMLButtonElement => {\n  const cls =\n    layout === \"rows\"\n      ? \"persona-ask-pill persona-ask-row persona-pointer-events-auto\"\n      : \"persona-ask-pill persona-pointer-events-auto\";\n  const btn = createElement(\"button\", cls) as HTMLButtonElement;\n  btn.type = \"button\";\n  btn.setAttribute(\"role\", multiSelect ? \"checkbox\" : \"button\");\n  btn.setAttribute(\"aria-pressed\", \"false\");\n  btn.setAttribute(\"data-ask-user-action\", \"pick\");\n  btn.setAttribute(\"data-option-index\", String(index));\n  btn.setAttribute(\"data-option-label\", option.label);\n\n  if (layout === \"rows\") {\n    const content = createElement(\"span\", \"persona-ask-row-content\");\n    const label = createElement(\"span\", \"persona-ask-row-label\");\n    label.textContent = option.label;\n    content.appendChild(label);\n    if (option.description) {\n      const desc = createElement(\"span\", \"persona-ask-row-description\");\n      desc.textContent = option.description;\n      content.appendChild(desc);\n    }\n    btn.appendChild(content);\n    const aff = buildAffordance(layout, multiSelect, index);\n    if (aff) btn.appendChild(aff);\n  } else {\n    btn.textContent = option.label;\n    if (option.description) btn.title = option.description;\n  }\n  return btn;\n};\n\nconst buildSkeletonPill = (layout: AskUserQuestionLayout): HTMLElement => {\n  const cls =\n    layout === \"rows\"\n      ? \"persona-ask-pill persona-ask-row persona-ask-pill-skeleton persona-pointer-events-none\"\n      : \"persona-ask-pill persona-ask-pill-skeleton persona-pointer-events-none\";\n  const el = createElement(\"span\", cls);\n  el.setAttribute(\"aria-hidden\", \"true\");\n  return el;\n};\n\n/**\n * Build the interactive pill list + optional free-text pill for a given prompt.\n */\nconst buildPillList = (\n  prompt: Partial<AskUserQuestionPrompt> | null,\n  feature: AgentWidgetAskUserQuestionFeature,\n  complete: boolean,\n  layout: AskUserQuestionLayout\n): HTMLElement => {\n  const baseClass =\n    layout === \"rows\"\n      ? \"persona-ask-pills persona-ask-pills--rows persona-flex persona-flex-col persona-gap-2\"\n      : \"persona-ask-pills persona-flex persona-flex-wrap persona-gap-2\";\n  const list = createElement(\"div\", baseClass);\n  list.setAttribute(\"role\", \"group\");\n  list.setAttribute(\"data-ask-pill-list\", \"true\");\n\n  const multiSelect = !!prompt?.multiSelect;\n  const realOptions = Array.isArray(prompt?.options) ? (prompt!.options as AskUserQuestionOption[]) : [];\n  const cleanOptions = realOptions.filter((o) => o && typeof o.label === \"string\" && o.label.length > 0);\n\n  if (cleanOptions.length === 0 && !complete) {\n    for (let i = 0; i < DEFAULT_SKELETON_PILLS; i++) {\n      list.appendChild(buildSkeletonPill(layout));\n    }\n    return list;\n  }\n\n  cleanOptions.forEach((option, index) => {\n    list.appendChild(buildPill(option, index, layout, multiSelect));\n  });\n\n  // Free-text affordance:\n  //   - Rows layout: a composite row that visually matches the option rows\n  //     and HAS the input inside it (no separate row below). Number badge\n  //     `N+1` on the right; pressing it focuses the input via the\n  //     `focus-free-text` action.\n  //   - Pills layout (legacy): a dashed pill button that expands a separate\n  //     input row on click (handled by `buildFreeTextRow`).\n  const allowFreeText = prompt?.allowFreeText !== false;\n  if (allowFreeText) {\n    const defaultLabel =\n      layout === \"rows\" ? DEFAULT_FREE_TEXT_LABEL_ROWS : DEFAULT_FREE_TEXT_LABEL_PILLS;\n    if (layout === \"rows\") {\n      const otherRow = createElement(\n        \"div\",\n        \"persona-ask-pill persona-ask-row persona-ask-row--other persona-ask-pill-custom persona-pointer-events-auto\"\n      );\n      otherRow.setAttribute(\"data-ask-user-action\", \"focus-free-text\");\n      otherRow.setAttribute(\"data-option-index\", String(cleanOptions.length));\n      otherRow.setAttribute(\"data-ask-other-row\", \"true\");\n\n      const content = createElement(\"span\", \"persona-ask-row-content\");\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.className = \"persona-ask-row-input persona-flex-1 persona-pointer-events-auto\";\n      input.placeholder = feature.freeTextPlaceholder ?? DEFAULT_FREE_TEXT_PLACEHOLDER;\n      input.setAttribute(\"data-ask-free-text-input\", \"true\");\n      input.setAttribute(\n        \"aria-label\",\n        feature.freeTextLabel ?? defaultLabel\n      );\n      content.appendChild(input);\n      otherRow.appendChild(content);\n\n      const aff = buildAffordance(layout, multiSelect, cleanOptions.length);\n      if (aff) otherRow.appendChild(aff);\n      list.appendChild(otherRow);\n    } else {\n      const freeBtn = createElement(\n        \"button\",\n        \"persona-ask-pill persona-ask-pill-custom persona-pointer-events-auto\"\n      ) as HTMLButtonElement;\n      freeBtn.type = \"button\";\n      freeBtn.setAttribute(\"data-ask-user-action\", \"open-free-text\");\n      freeBtn.textContent = feature.freeTextLabel ?? defaultLabel;\n      list.appendChild(freeBtn);\n    }\n  }\n\n  return list;\n};\n\nconst buildFreeTextRow = (\n  feature: AgentWidgetAskUserQuestionFeature,\n  layout: AskUserQuestionLayout\n): HTMLElement => {\n  const cls =\n    layout === \"rows\"\n      ? \"persona-ask-free-text persona-ask-free-text--rows persona-flex persona-gap-2 persona-mt-2\"\n      : \"persona-ask-free-text persona-hidden persona-flex persona-gap-2 persona-mt-2\";\n  const row = createElement(\"div\", cls);\n  row.setAttribute(\"data-ask-free-text-row\", \"true\");\n\n  const input = document.createElement(\"input\");\n  input.type = \"text\";\n  input.className =\n    \"persona-ask-free-text-input persona-flex-1 persona-pointer-events-auto\";\n  input.placeholder = feature.freeTextPlaceholder ?? DEFAULT_FREE_TEXT_PLACEHOLDER;\n  input.setAttribute(\"data-ask-free-text-input\", \"true\");\n\n  row.appendChild(input);\n\n  // Pills (legacy) layout keeps the explicit Send button so the expand-on-click\n  // affordance has a commit target. Rows layout drops it: the input commits via\n  // Enter, or via the grouped Next/Submit-all flush path.\n  if (layout !== \"rows\") {\n    const submit = createElement(\n      \"button\",\n      \"persona-ask-free-text-submit persona-pointer-events-auto\"\n    ) as HTMLButtonElement;\n    submit.type = \"button\";\n    submit.textContent = feature.submitLabel ?? DEFAULT_SUBMIT_LABEL;\n    submit.setAttribute(\"data-ask-user-action\", \"submit-free-text\");\n    row.appendChild(submit);\n  }\n\n  return row;\n};\n\nconst buildMultiSelectActions = (\n  feature: AgentWidgetAskUserQuestionFeature\n): HTMLElement => {\n  const row = createElement(\n    \"div\",\n    \"persona-ask-multi-actions persona-flex persona-justify-end persona-mt-2\"\n  );\n  row.setAttribute(\"data-ask-multi-actions\", \"true\");\n\n  const submit = createElement(\n    \"button\",\n    \"persona-ask-multi-submit persona-pointer-events-auto\"\n  ) as HTMLButtonElement;\n  submit.type = \"button\";\n  submit.textContent = feature.submitLabel ?? DEFAULT_SUBMIT_LABEL;\n  submit.setAttribute(\"data-ask-user-action\", \"submit-multi\");\n  submit.disabled = true;\n\n  row.appendChild(submit);\n  return row;\n};\n\nconst buildNavRow = (\n  index: number,\n  count: number,\n  feature: AgentWidgetAskUserQuestionFeature\n): HTMLElement => {\n  const row = createElement(\n    \"div\",\n    \"persona-ask-nav persona-flex persona-justify-between persona-items-center persona-gap-2 persona-mt-2\"\n  );\n  row.setAttribute(\"data-ask-nav-row\", \"true\");\n\n  const back = createElement(\n    \"button\",\n    \"persona-ask-nav-back persona-pointer-events-auto\"\n  ) as HTMLButtonElement;\n  back.type = \"button\";\n  back.textContent = feature.backLabel ?? DEFAULT_BACK_LABEL;\n  back.setAttribute(\"data-ask-user-action\", \"back\");\n  back.disabled = index === 0;\n  row.appendChild(back);\n\n  const rightGroup = createElement(\n    \"div\",\n    \"persona-ask-nav-right persona-flex persona-items-center persona-gap-2\"\n  );\n\n  const skip = createElement(\n    \"button\",\n    \"persona-ask-nav-skip persona-pointer-events-auto\"\n  ) as HTMLButtonElement;\n  skip.type = \"button\";\n  skip.textContent = feature.skipLabel ?? DEFAULT_SKIP_LABEL;\n  skip.setAttribute(\"data-ask-user-action\", \"skip\");\n  rightGroup.appendChild(skip);\n\n  const next = createElement(\n    \"button\",\n    \"persona-ask-nav-next persona-pointer-events-auto\"\n  ) as HTMLButtonElement;\n  next.type = \"button\";\n  const isFinal = index === count - 1;\n  next.textContent = isFinal\n    ? feature.submitAllLabel ?? DEFAULT_SUBMIT_ALL_LABEL\n    : feature.nextLabel ?? DEFAULT_NEXT_LABEL;\n  next.setAttribute(\"data-ask-user-action\", isFinal ? \"submit-all\" : \"next\");\n  next.disabled = true; // updated by syncNavState\n  rightGroup.appendChild(next);\n\n  row.appendChild(rightGroup);\n\n  return row;\n};\n\n/**\n * Read the answers map stored on the sheet element.\n */\nexport const readAnswersFromSheet = (\n  sheet: HTMLElement\n): Record<number, string | string[]> => {\n  const raw = sheet.getAttribute(ATTR_ANSWERS);\n  if (!raw) return {};\n  try {\n    const parsed = JSON.parse(raw);\n    return parsed && typeof parsed === \"object\" ? (parsed as Record<number, string | string[]>) : {};\n  } catch {\n    return {};\n  }\n};\n\n/**\n * Write the answers map back to the sheet element.\n */\nexport const writeAnswersToSheet = (\n  sheet: HTMLElement,\n  answers: Record<number, string | string[]>\n): void => {\n  sheet.setAttribute(ATTR_ANSWERS, JSON.stringify(answers));\n};\n\nexport const getCurrentIndex = (sheet: HTMLElement): number => {\n  const raw = Number(sheet.getAttribute(ATTR_CURRENT_INDEX) ?? \"0\");\n  return Number.isFinite(raw) ? Math.max(0, Math.floor(raw)) : 0;\n};\n\nexport const setCurrentIndex = (sheet: HTMLElement, index: number): void => {\n  sheet.setAttribute(ATTR_CURRENT_INDEX, String(Math.max(0, Math.floor(index))));\n};\n\nexport const getQuestionCount = (sheet: HTMLElement): number => {\n  const raw = Number(sheet.getAttribute(ATTR_QUESTION_COUNT) ?? \"1\");\n  return Number.isFinite(raw) ? Math.max(1, Math.floor(raw)) : 1;\n};\n\nexport const isGroupedSheet = (sheet: HTMLElement): boolean => {\n  return sheet.getAttribute(ATTR_GROUPED) === \"true\";\n};\n\nconst restoreAnswersFromMessage = (\n  message: AgentWidgetMessage,\n  prompts: Partial<AskUserQuestionPrompt>[]\n): Record<number, string | string[]> => {\n  const stored = message.agentMetadata?.askUserQuestionAnswers;\n  if (!stored || typeof stored !== \"object\") return {};\n  const result: Record<number, string | string[]> = {};\n  prompts.forEach((p, i) => {\n    const q = typeof p?.question === \"string\" ? p.question : \"\";\n    if (q && Object.prototype.hasOwnProperty.call(stored, q)) {\n      const v = stored[q];\n      if (typeof v === \"string\" || Array.isArray(v)) {\n        result[i] = v;\n      }\n    }\n  });\n  return result;\n};\n\nconst restoreIndexFromMessage = (\n  message: AgentWidgetMessage,\n  count: number\n): number => {\n  const stored = message.agentMetadata?.askUserQuestionIndex;\n  if (typeof stored !== \"number\" || !Number.isFinite(stored)) return 0;\n  return Math.max(0, Math.min(count - 1, Math.floor(stored)));\n};\n\n/**\n * Keyed-by-question-text view of the current answers on a sheet. Used both for\n * persistence to message metadata and for the final tool-result payload sent\n * back to the agent.\n */\nexport const buildStructuredAnswers = (\n  sheet: HTMLElement,\n  message: AgentWidgetMessage\n): Record<string, string | string[]> => {\n  const { payload } = parseAskUserQuestionPayload(message);\n  const prompts = promptsFromPayload(payload);\n  const indexed = readAnswersFromSheet(sheet);\n  const result: Record<string, string | string[]> = {};\n  const seen = new Set<string>();\n  prompts.forEach((p, i) => {\n    const q = typeof p?.question === \"string\" ? p.question : \"\";\n    if (!q) return;\n    if (seen.has(q) && typeof console !== \"undefined\") {\n      // eslint-disable-next-line no-console\n      console.warn(`[AgentWidget] ask_user_question has duplicate question text \"${q}\"; later answer wins.`);\n    }\n    seen.add(q);\n    if (Object.prototype.hasOwnProperty.call(indexed, i)) {\n      result[q] = indexed[i];\n    }\n  });\n  return result;\n};\n\n/**\n * Apply the selected/unselected visual state to pills on the current page,\n * based on the answer stored for `currentIndex`.\n */\nconst applySelectionState = (sheet: HTMLElement): void => {\n  const answers = readAnswersFromSheet(sheet);\n  const currentIndex = getCurrentIndex(sheet);\n  const stored = answers[currentIndex];\n  const selected = new Set<string>();\n  if (typeof stored === \"string\") selected.add(stored);\n  else if (Array.isArray(stored)) stored.forEach((s) => selected.add(s));\n\n  const pills = sheet.querySelectorAll<HTMLButtonElement>('[data-ask-user-action=\"pick\"][data-option-label]');\n  pills.forEach((pill) => {\n    const label = pill.getAttribute(\"data-option-label\") ?? \"\";\n    const on = selected.has(label);\n    pill.setAttribute(\"aria-pressed\", on ? \"true\" : \"false\");\n    pill.classList.toggle(\"persona-ask-pill-selected\", on);\n  });\n\n  // Also pre-fill the free-text input if the saved answer doesn't match any pill.\n  const realPillLabels = new Set(\n    Array.from(pills).map((p) => p.getAttribute(\"data-option-label\") ?? \"\")\n  );\n  // In rows mode the input lives inside the Other row of the pill list; in\n  // pills mode it lives in a separate (potentially hidden) free-text row.\n  // Querying the input directly covers both layouts.\n  const freeInput = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n  if (freeInput) {\n    if (typeof stored === \"string\" && stored.length > 0 && !realPillLabels.has(stored)) {\n      freeInput.value = stored;\n      const freeRow = freeInput.closest<HTMLElement>('[data-ask-free-text-row=\"true\"]');\n      freeRow?.classList.remove(\"persona-hidden\");\n    } else {\n      freeInput.value = \"\";\n    }\n  }\n};\n\n/**\n * Update the Next/Submit-all enabled state based on whether the current\n * question has a non-empty answer stored.\n */\nconst syncNavState = (sheet: HTMLElement): void => {\n  if (!isGroupedSheet(sheet)) return;\n  const answers = readAnswersFromSheet(sheet);\n  const currentIndex = getCurrentIndex(sheet);\n  const v = answers[currentIndex];\n  const hasAnswer =\n    (typeof v === \"string\" && v.length > 0) || (Array.isArray(v) && v.length > 0);\n  const next = sheet.querySelector<HTMLButtonElement>(\n    '[data-ask-user-action=\"next\"], [data-ask-user-action=\"submit-all\"]'\n  );\n  if (next) next.disabled = !hasAnswer;\n\n  // Multi-select submit (1-question mode): keep existing behavior.\n  const multi = sheet.querySelector<HTMLButtonElement>('[data-ask-user-action=\"submit-multi\"]');\n  if (multi) {\n    const labels = Array.from(\n      sheet.querySelectorAll<HTMLElement>('[aria-pressed=\"true\"][data-option-label]')\n    );\n    multi.disabled = labels.length === 0;\n  }\n};\n\n/**\n * Replace the page-scoped body of the sheet (question text, pills, free-text\n * row, multi-select actions) with content for `currentIndex`. Called both on\n * initial mount and after every Back/Next navigation. Preserves the stepper\n * row, the dismiss button, and the nav row at the bottom.\n */\nconst renderCurrentPage = (\n  sheet: HTMLElement,\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig | undefined\n): void => {\n  const feature = resolveFeature(config);\n  const layout = getLayout(sheet);\n  const { payload, complete } = parseAskUserQuestionPayload(message);\n  const grouped = isGroupedSheet(sheet);\n  const index = getCurrentIndex(sheet);\n  const count = getQuestionCount(sheet);\n  const prompt = grouped ? promptAt(payload, index) : firstPrompt(payload);\n  const multiSelect = !!prompt?.multiSelect;\n\n  // Inline stepper \"{index+1}/{count}\" lives in the header next to the\n  // question text. Empty in single-Q mode.\n  const stepInline = sheet.querySelector<HTMLElement>('[data-ask-step-inline=\"true\"]');\n  if (stepInline) {\n    stepInline.textContent = grouped ? `${index + 1}/${count}` : \"\";\n  }\n  // Sweep any legacy stepper row from earlier renders.\n  const oldStepper = sheet.querySelector<HTMLElement>('[data-ask-stepper=\"true\"]');\n  if (oldStepper) oldStepper.remove();\n\n  // Question text\n  const qText = sheet.querySelector<HTMLElement>('[data-ask-question=\"true\"]');\n  if (qText) {\n    const text = typeof prompt?.question === \"string\" ? prompt.question : \"\";\n    qText.textContent = text;\n    qText.classList.toggle(\"persona-ask-question-skeleton\", !text && !complete);\n  }\n\n  // Pills list\n  const pillList = sheet.querySelector<HTMLElement>('[data-ask-pill-list=\"true\"]');\n  if (pillList) {\n    const fresh = buildPillList(prompt, feature, complete, layout);\n    pillList.replaceWith(fresh);\n  }\n\n  // Free-text row: re-build to clear stale input value across pages.\n  // Only present in pills (legacy) mode; in rows mode the input lives inside\n  // the Other row of the pill list, which is rebuilt above.\n  if (layout !== \"rows\") {\n    const oldFree = sheet.querySelector<HTMLElement>('[data-ask-free-text-row=\"true\"]');\n    if (oldFree) oldFree.replaceWith(buildFreeTextRow(feature, layout));\n  }\n\n  // Multi-select action row: only relevant in 1-question mode.\n  const oldMulti = sheet.querySelector<HTMLElement>('[data-ask-multi-actions=\"true\"]');\n  if (!grouped && multiSelect && !oldMulti) {\n    sheet.appendChild(buildMultiSelectActions(feature));\n  } else if ((!multiSelect || grouped) && oldMulti) {\n    oldMulti.remove();\n  }\n  sheet.setAttribute(\"data-multi-select\", multiSelect ? \"true\" : \"false\");\n\n  // Nav row stays last; only present in grouped mode.\n  const oldNav = sheet.querySelector<HTMLElement>('[data-ask-nav-row=\"true\"]');\n  if (grouped) {\n    const fresh = buildNavRow(index, count, feature);\n    if (oldNav) oldNav.replaceWith(fresh);\n    else sheet.appendChild(fresh);\n  } else if (oldNav) {\n    oldNav.remove();\n  }\n\n  applySelectionState(sheet);\n  syncNavState(sheet);\n};\n\nconst buildSheet = (\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig | undefined,\n  payload: Partial<AskUserQuestionPayload> | null\n): HTMLElement => {\n  const feature = resolveFeature(config);\n  const layout = resolveLayout(feature);\n  const toolCallId = message.toolCall!.id;\n  const prompts = promptsFromPayload(payload);\n  const count = Math.max(1, prompts.length);\n  const grouped = count > 1;\n\n  const initialAnswers = restoreAnswersFromMessage(message, prompts);\n  const initialIndex = grouped ? restoreIndexFromMessage(message, count) : 0;\n\n  const sheet = createElement(\n    \"div\",\n    [\n      \"persona-ask-sheet\",\n      `persona-ask-sheet--${layout}`,\n      \"persona-pointer-events-auto\",\n      \"persona-ask-sheet-enter\",\n    ].join(\" \")\n  );\n  sheet.setAttribute(SHEET_SENTINEL, toolCallId);\n  sheet.setAttribute(\"data-tool-call-id\", toolCallId);\n  sheet.setAttribute(\"data-message-id\", message.id);\n  sheet.setAttribute(ATTR_QUESTION_COUNT, String(count));\n  sheet.setAttribute(ATTR_CURRENT_INDEX, String(initialIndex));\n  sheet.setAttribute(ATTR_GROUPED, grouped ? \"true\" : \"false\");\n  sheet.setAttribute(ATTR_LAYOUT, layout);\n  writeAnswersToSheet(sheet, initialAnswers);\n  sheet.setAttribute(\"role\", \"group\");\n  sheet.setAttribute(\"aria-label\", \"Suggested answers\");\n\n  if (feature.slideInMs !== undefined) {\n    sheet.style.setProperty(\"--persona-ask-sheet-duration\", `${feature.slideInMs}ms`);\n  }\n  applyStyleVars(sheet, feature);\n\n  // Header: question text (flex-1) + compact \"N/M\" stepper indicator on the\n  // right (grouped only). Skip in the nav row is the canonical escape hatch\n  //: plugins that want a different escape model render their own UX.\n  const header = createElement(\n    \"div\",\n    \"persona-ask-sheet-header persona-flex persona-items-center persona-gap-3\"\n  );\n\n  const qText = createElement(\"div\", \"persona-ask-sheet-question persona-flex-1\");\n  qText.setAttribute(\"data-ask-question\", \"true\");\n  qText.textContent = \"\";\n  header.appendChild(qText);\n\n  // Inline stepper indicator. Empty for single-Q; populated by\n  // renderCurrentPage to \"{index+1}/{count}\" in grouped mode.\n  const stepInline = createElement(\n    \"span\",\n    \"persona-ask-sheet-step-inline\"\n  );\n  stepInline.setAttribute(\"data-ask-step-inline\", \"true\");\n  stepInline.textContent = \"\";\n  header.appendChild(stepInline);\n\n  sheet.appendChild(header);\n\n  // Skeleton placeholders: these get replaced wholesale by renderCurrentPage.\n  const skeletonClass =\n    layout === \"rows\"\n      ? \"persona-ask-pills persona-ask-pills--rows persona-flex persona-flex-col persona-gap-2\"\n      : \"persona-ask-pills persona-flex persona-flex-wrap persona-gap-2\";\n  const list = createElement(\"div\", skeletonClass);\n  list.setAttribute(\"data-ask-pill-list\", \"true\");\n  list.setAttribute(\"role\", \"group\");\n  sheet.appendChild(list);\n\n  // Pills (legacy) layout uses a separate, hidden free-text row that expands\n  // on click. Rows layout embeds the input inside the Other row of the pill\n  // list, so the standalone row is unnecessary.\n  if (layout !== \"rows\") {\n    sheet.appendChild(buildFreeTextRow(feature, layout));\n  }\n\n  // Render the actual current page (stepper, pills, multi-actions, nav).\n  renderCurrentPage(sheet, message, config);\n\n  // Remove the enter class next frame so the slide-in transition runs.\n  requestAnimationFrame(() => {\n    requestAnimationFrame(() => sheet.classList.remove(\"persona-ask-sheet-enter\"));\n  });\n\n  return sheet;\n};\n\nconst syncSheetFromMessage = (\n  sheet: HTMLElement,\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig | undefined\n): void => {\n  // If the payload's question count grew (rare mid-stream), update the cached count.\n  const { payload } = parseAskUserQuestionPayload(message);\n  const newCount = Math.max(1, promptsFromPayload(payload).length);\n  if (newCount > getQuestionCount(sheet)) {\n    sheet.setAttribute(ATTR_QUESTION_COUNT, String(newCount));\n    if (newCount > 1 && !isGroupedSheet(sheet)) {\n      sheet.setAttribute(ATTR_GROUPED, \"true\");\n    }\n  }\n  renderCurrentPage(sheet, message, config);\n};\n\n/**\n * Create the small in-transcript stub for an `ask_user_question` tool call.\n * The stub is passive: the interactive sheet is mounted separately into\n * the composer overlay via `ensureAskUserQuestionSheet`.\n */\nexport const createAskUserQuestionBubble = (\n  message: AgentWidgetMessage,\n  config?: AgentWidgetConfig\n): HTMLElement => {\n  const bubble = createElement(\n    \"div\",\n    \"persona-ask-stub persona-inline-flex persona-items-center persona-gap-2\"\n  );\n  bubble.id = `bubble-${message.id}`;\n  bubble.setAttribute(\"data-message-id\", message.id);\n  bubble.setAttribute(\"data-bubble-type\", \"ask-user-question\");\n\n  const feature = resolveFeature(config);\n  applyStyleVars(bubble, feature);\n\n  const text = createElement(\"span\", \"persona-ask-stub-label\");\n  const { complete } = parseAskUserQuestionPayload(message);\n  text.textContent = complete ? \"Awaiting your response…\" : \"Preparing options…\";\n  bubble.appendChild(text);\n\n  return bubble;\n};\n\n/**\n * Mount or update the interactive answer-pill sheet for a given message.\n * Idempotent: if a sheet already exists for the tool-call id, it is hydrated\n * in-place instead of remounted, so streaming updates don't flicker.\n */\nexport const ensureAskUserQuestionSheet = (\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig | undefined,\n  overlay: HTMLElement | null | undefined\n): void => {\n  if (!overlay) return;\n  if (!isAskUserQuestionMessage(message)) return;\n\n  const feature = resolveFeature(config);\n  if (feature.enabled === false) return;\n\n  const toolCallId = message.toolCall!.id;\n\n  // Only keep the latest sheet in the overlay: clear any stale siblings.\n  const siblings = overlay.querySelectorAll<HTMLElement>(`[${SHEET_SENTINEL}]`);\n  siblings.forEach((el) => {\n    if (el.getAttribute(SHEET_SENTINEL) !== toolCallId) {\n      el.remove();\n    }\n  });\n\n  const existing = overlay.querySelector<HTMLElement>(\n    `[${SHEET_SENTINEL}=\"${escapeAttrValue(toolCallId)}\"]`\n  );\n  if (existing) {\n    syncSheetFromMessage(existing, message, config);\n    return;\n  }\n\n  const { payload } = parseAskUserQuestionPayload(message);\n  const sheet = buildSheet(message, config, payload);\n  overlay.appendChild(sheet);\n};\n\n/**\n * Remove the sheet for a specific tool-call id, or all sheets if omitted.\n * Runs a slide-out transition before removing.\n */\nexport const removeAskUserQuestionSheet = (\n  overlay: HTMLElement | null | undefined,\n  toolCallId?: string\n): void => {\n  if (!overlay) return;\n\n  const selector = toolCallId\n    ? `[${SHEET_SENTINEL}=\"${escapeAttrValue(toolCallId)}\"]`\n    : `[${SHEET_SENTINEL}]`;\n  const sheets = overlay.querySelectorAll<HTMLElement>(selector);\n\n  sheets.forEach((sheet) => {\n    sheet.classList.add(\"persona-ask-sheet-leave\");\n    const duration = Number.parseInt(\n      getComputedStyle(sheet).getPropertyValue(\"--persona-ask-sheet-duration\") || \"180\",\n      10\n    );\n    const remove = () => sheet.remove();\n    setTimeout(remove, Number.isFinite(duration) ? duration : 180);\n  });\n};\n\n/**\n * Read the currently-selected option labels from a multi-select sheet.\n */\nexport const getSelectedLabels = (sheet: HTMLElement): string[] => {\n  return Array.from(\n    sheet.querySelectorAll<HTMLElement>('[aria-pressed=\"true\"][data-option-label]')\n  )\n    .map((el) => el.getAttribute(\"data-option-label\"))\n    .filter((label): label is string => typeof label === \"string\" && label.length > 0);\n};\n\n/**\n * Update the answer for the current page and refresh visual state. Used by\n * the ui.ts event handlers in grouped mode.\n */\nexport const setCurrentAnswer = (\n  sheet: HTMLElement,\n  answer: string | string[]\n): void => {\n  const answers = readAnswersFromSheet(sheet);\n  const idx = getCurrentIndex(sheet);\n  if (typeof answer === \"string\" && answer.length === 0) {\n    delete answers[idx];\n  } else if (Array.isArray(answer) && answer.length === 0) {\n    delete answers[idx];\n  } else {\n    answers[idx] = answer;\n  }\n  writeAnswersToSheet(sheet, answers);\n  applySelectionState(sheet);\n  syncNavState(sheet);\n};\n\n/**\n * Navigate to a page by index and re-render the current page contents.\n */\nexport const navigateToPage = (\n  sheet: HTMLElement,\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig | undefined,\n  index: number\n): void => {\n  const count = getQuestionCount(sheet);\n  const clamped = Math.max(0, Math.min(count - 1, index));\n  setCurrentIndex(sheet, clamped);\n  renderCurrentPage(sheet, message, config);\n};\n\n/**\n * Re-export of the post-render nav-state sync, for ui.ts to call after pill\n * toggles in grouped multi-select mode.\n */\nexport const refreshNavState = (sheet: HTMLElement): void => {\n  syncNavState(sheet);\n};\n\n/**\n * Test seam: reset the one-shot truncation warning so each test can assert\n * the warn fires exactly once.\n */\nexport const __resetTruncateWarn = (): void => {\n  truncateWarned = false;\n};\n","/**\n * DOM utility functions\n */\nexport const createElement = <K extends keyof HTMLElementTagNameMap>(\n  tag: K,\n  className?: string\n): HTMLElementTagNameMap[K] => {\n  const element = document.createElement(tag);\n  if (className) {\n    element.className = className;\n  }\n  return element;\n};\n\nexport const createElementInDocument = <K extends keyof HTMLElementTagNameMap>(\n  documentRef: Document,\n  tag: K,\n  className?: string\n): HTMLElementTagNameMap[K] => {\n  const element = documentRef.createElement(tag);\n  if (className) {\n    element.className = className;\n  }\n  return element;\n};\n\nexport const createFragment = (): DocumentFragment => {\n  return document.createDocumentFragment();\n};\n\nexport interface CreateNodeOptions {\n  /** Sets `element.className`. */\n  className?: string;\n  /** Sets `element.textContent` before any `children` are appended. */\n  text?: string;\n  /** Attribute name → value pairs applied via `setAttribute`. */\n  attrs?: Record<string, string>;\n  /**\n   * Inline styles. Nullish (`undefined`/`null`) values are skipped so callers\n   * can inline conditionals (e.g. `borderColor: cfg.borderColor`) without an\n   * `if` per property. Note this only *sets* values, it never clears them, so\n   * prefer it for constructing fresh elements rather than re-styling live ones.\n   */\n  style?: Partial<CSSStyleDeclaration>;\n}\n\n/**\n * Ergonomic element factory that bundles the className + attribute + style +\n * children boilerplate the widget's DOM builders otherwise repeat by hand.\n *\n * `createElement` stays the right tool for the simple `(tag, className)` case;\n * reach for `createNode` when a node also needs attributes, inline styles, or\n * up-front children. Nullish `children` are skipped so callers can inline\n * conditionals (e.g. `maybeIcon && el`).\n */\nexport const createNode = <K extends keyof HTMLElementTagNameMap>(\n  tag: K,\n  options: CreateNodeOptions = {},\n  ...children: Array<Node | string | null | undefined>\n): HTMLElementTagNameMap[K] => {\n  const element = document.createElement(tag);\n\n  if (options.className) {\n    element.className = options.className;\n  }\n  if (options.text !== undefined) {\n    element.textContent = options.text;\n  }\n  if (options.attrs) {\n    for (const [name, value] of Object.entries(options.attrs)) {\n      element.setAttribute(name, value);\n    }\n  }\n  if (options.style) {\n    const style = element.style as unknown as Record<string, string>;\n    const source = options.style as Record<string, string | null | undefined>;\n    for (const property of Object.keys(source)) {\n      const value = source[property];\n      if (value != null) {\n        style[property] = value;\n      }\n    }\n  }\n\n  const appendable = children.filter(\n    (child): child is Node | string => child != null\n  );\n  if (appendable.length > 0) {\n    element.append(...appendable);\n  }\n\n  return element;\n};\n\n/**\n * Join truthy class-name fragments into a single space-separated string\n * (the clsx / classnames pattern). Falsy fragments\n * (`false` / `null` / `undefined` / `\"\"`) are dropped, so conditional classes\n * read inline as `cond && \"persona-foo\"` instead of imperative\n * `classList.add(...)` branches.\n */\nexport const cx = (\n  ...parts: Array<string | false | null | undefined>\n): string => parts.filter(Boolean).join(\" \");\n\n\n\n\n\n\n\n","/**\n * Built-in `suggest_replies` client tool.\n *\n * The widget can advertise this tool to the agent on every dispatch via\n * `clientTools[]` (set `features.suggestReplies.expose: true`): the same\n * wire surface as `ask_user_question` and WebMCP page tools. When the model\n * calls it, the execution pauses with a `step_await`\n * (`awaitReason: \"local_tool_required\"`); unlike `ask_user_question`, the\n * widget resolves it FIRE-AND-FORGET: it renders the suggestions as tappable\n * chips above the composer and immediately resumes the execution with a\n * canned \"shown\" result, so the agent's turn completes without waiting on the\n * user. Tapping a chip sends its text verbatim as the user's next message.\n *\n * Chip visibility is DERIVED state, not imperative show/hide: the chips of\n * the last `suggest_replies` tool message with no user message after it are\n * shown (see {@link latestAgentSuggestions}). That single rule covers\n * soft-dismiss on any user message (typed, voice, or chip click), restore on\n * reload/hydration, and latest-wins when a turn carries multiple calls.\n */\n\nimport type {\n  AgentWidgetConfig,\n  AgentWidgetMessage,\n  ClientToolDefinition,\n} from \"./types\";\n\nexport const SUGGEST_REPLIES_TOOL_NAME = \"suggest_replies\";\n\n/** Renderer cap: payloads beyond this are truncated with a console warning. */\nexport const SUGGEST_REPLIES_MAX = 4;\n\n/**\n * JSON Schema for the tool's parameters. Mirrors what\n * {@link parseSuggestRepliesPayload} hydrates the chips from, so the schema\n * the model is held to and the shape the renderer expects can never drift.\n */\nexport const SUGGEST_REPLIES_PARAMETERS_SCHEMA = {\n  type: \"object\",\n  properties: {\n    suggestions: {\n      type: \"array\",\n      minItems: 1,\n      maxItems: SUGGEST_REPLIES_MAX,\n      description:\n        \"1-4 short, distinct follow-up replies, phrased in the user's voice.\",\n      items: { type: \"string\", minLength: 1, maxLength: 60 },\n    },\n  },\n  required: [\"suggestions\"],\n  additionalProperties: false,\n} as const;\n\n/**\n * The `ClientToolDefinition` shipped on `dispatch.clientTools[]` when\n * `features.suggestReplies.expose` is on. Exported so integrators who prefer\n * declaring the tool server-side (a flow's `runtimeTools`) can reuse the same\n * description and schema instead of hand-writing them.\n */\nexport const SUGGEST_REPLIES_CLIENT_TOOL: ClientToolDefinition = {\n  name: SUGGEST_REPLIES_TOOL_NAME,\n  description:\n    \"Offer the user tappable quick-reply suggestions for their next message. \" +\n    \"Call at most once per turn, as the LAST action after your reply text is \" +\n    \"complete. Each suggestion is sent verbatim as the user's next message, \" +\n    'so phrase suggestions in the user\\'s voice (e.g. \"Tell me more about ' +\n    'pricing\"). Keep them short and distinct. The result only confirms the ' +\n    \"suggestions were shown: do not add further commentary after calling \" +\n    \"this tool; end your turn.\",\n  parametersSchema: SUGGEST_REPLIES_PARAMETERS_SCHEMA,\n  origin: \"sdk\",\n  annotations: { readOnlyHint: true },\n};\n\n/**\n * The canned tool output posted to `/resume` the moment the chips render.\n * MCP content shape, matching what the WebMCP resume path posts for page\n * tools. Built fresh per call so a caller can't mutate a shared object.\n */\nexport const suggestRepliesToolResult = (): {\n  content: { type: \"text\"; text: string }[];\n} => ({\n  content: [{ type: \"text\", text: \"Suggestions shown to the user.\" }],\n});\n\n/** A tool-variant message produced by a `suggest_replies` call. */\nexport const isSuggestRepliesMessage = (\n  message: AgentWidgetMessage,\n): boolean =>\n  message.variant === \"tool\" &&\n  message.toolCall?.name === SUGGEST_REPLIES_TOOL_NAME;\n\n/**\n * Tolerant parse of a `suggest_replies` tool call's args into chip labels:\n * accepts a JSON string or object, coerces items to trimmed strings, drops\n * empties, and truncates past the renderer cap with a console warning.\n */\nexport const parseSuggestRepliesPayload = (args: unknown): string[] => {\n  let parsed: unknown = args;\n  if (typeof parsed === \"string\") {\n    try {\n      parsed = JSON.parse(parsed);\n    } catch {\n      return [];\n    }\n  }\n  const raw = (parsed as { suggestions?: unknown } | null | undefined)\n    ?.suggestions;\n  if (!Array.isArray(raw)) return [];\n  const chips = raw\n    .filter((item): item is string => typeof item === \"string\")\n    .map((item) => item.trim())\n    .filter((item) => item.length > 0);\n  if (chips.length > SUGGEST_REPLIES_MAX) {\n    console.warn(\n      `[persona] suggest_replies: ${chips.length} suggestions exceeds the cap of ${SUGGEST_REPLIES_MAX}; extra suggestions dropped.`,\n    );\n    return chips.slice(0, SUGGEST_REPLIES_MAX);\n  }\n  return chips;\n};\n\n/**\n * The chips to show right now: those of the LAST `suggest_replies` tool\n * message with NO user message after it, or `null` when none apply. All\n * calls in a turn still get resumed (the server awaits each); only the\n * latest renders.\n */\nexport const latestAgentSuggestions = (\n  messages: AgentWidgetMessage[],\n): string[] | null => {\n  for (let i = messages.length - 1; i >= 0; i--) {\n    const message = messages[i];\n    if (message.role === \"user\") return null;\n    if (!isSuggestRepliesMessage(message)) continue;\n    const chips = parseSuggestRepliesPayload(message.toolCall?.args);\n    return chips.length > 0 ? chips : null;\n  }\n  return null;\n};\n\n/**\n * Gate for advertising the tool: `expose` opts it into the agent's catalog,\n * and `enabled !== false` guarantees the widget will actually auto-resolve\n * and render chips for it: exposing the tool with the feature disabled\n * would park the execution on a generic tool bubble with no resume coming.\n */\nexport const shouldExposeSuggestReplies = (\n  config: AgentWidgetConfig | undefined,\n): boolean => {\n  const feature = config?.features?.suggestReplies;\n  return feature?.expose === true && feature.enabled !== false;\n};\n","/**\n * Built-in `ask_user_question` client tool.\n *\n * The widget can advertise this tool to the agent on every dispatch via\n * `clientTools[]` (set `features.askUserQuestion.expose: true`): the same\n * wire surface WebMCP page tools ride. The server registers it as a LOCAL\n * tool under its bare name (`origin: 'sdk'` tools are not `webmcp:`-prefixed),\n * so when the model calls it the execution pauses with a `step_await`\n * (`awaitReason: \"local_tool_required\"`), the widget's answer-pill sheet\n * renders, and `session.resolveAskUserQuestion()` resumes the execution with\n * the structured answers.\n *\n * This replaces the previous integrator burden of hand-declaring the tool in\n * a flow's `runtimeTools` and keeping that schema in sync with the renderer.\n * Flows that already declare `ask_user_question` server-side should leave\n * `expose` off: the model would otherwise see the tool twice.\n */\n\nimport {\n  ASK_USER_QUESTION_MAX,\n  ASK_USER_QUESTION_TOOL_NAME,\n} from \"./components/ask-user-question-bubble\";\nimport {\n  SUGGEST_REPLIES_CLIENT_TOOL,\n  shouldExposeSuggestReplies,\n} from \"./suggest-replies-tool\";\nimport type { AgentWidgetConfig, ClientToolDefinition } from \"./types\";\n\n/**\n * JSON Schema for the tool's parameters. Mirrors {@link AskUserQuestionPayload}\n *, the shape `parseAskUserQuestionPayload` hydrates the answer sheet from,  * so the schema the model is held to and the schema the renderer expects can\n * never drift.\n */\nexport const ASK_USER_QUESTION_PARAMETERS_SCHEMA = {\n  type: \"object\",\n  properties: {\n    questions: {\n      type: \"array\",\n      minItems: 1,\n      maxItems: ASK_USER_QUESTION_MAX,\n      description: \"Questions to ask the user. Prefer a single question.\",\n      items: {\n        type: \"object\",\n        properties: {\n          question: {\n            type: \"string\",\n            description: \"The complete question, ending with a question mark.\",\n          },\n          header: {\n            type: \"string\",\n            maxLength: 12,\n            description: 'Short topic label, e.g. \"Auth method\".',\n          },\n          options: {\n            type: \"array\",\n            minItems: 2,\n            maxItems: 4,\n            description:\n              '2-4 distinct choices. Do NOT add an \"Other\" option: free text is automatic.',\n            items: {\n              type: \"object\",\n              properties: {\n                label: {\n                  type: \"string\",\n                  description: \"Concise choice text (1-5 words).\",\n                },\n                description: {\n                  type: \"string\",\n                  description: \"What the option means or implies.\",\n                },\n              },\n              required: [\"label\"],\n              additionalProperties: false,\n            },\n          },\n          multiSelect: {\n            type: \"boolean\",\n            description: \"Allow selecting multiple options. Default false.\",\n          },\n          allowFreeText: {\n            type: \"boolean\",\n            description: \"Show a free-text input. Default true.\",\n          },\n        },\n        required: [\"question\", \"options\"],\n        additionalProperties: false,\n      },\n    },\n  },\n  required: [\"questions\"],\n  additionalProperties: false,\n} as const;\n\n/**\n * The `ClientToolDefinition` shipped on `dispatch.clientTools[]` when\n * `features.askUserQuestion.expose` is on. Exported so integrators who prefer\n * declaring the tool server-side (a flow's `runtimeTools`) can reuse the same\n * description and schema instead of hand-writing them.\n */\nexport const ASK_USER_QUESTION_CLIENT_TOOL: ClientToolDefinition = {\n  name: ASK_USER_QUESTION_TOOL_NAME,\n  description:\n    \"Ask the user multiple-choice questions and wait for their answers. Use \" +\n    \"only when blocked on a decision that is the user's to make: a \" +\n    \"preference, a choice between valid approaches, or information you \" +\n    \"cannot infer. Each question offers 2-4 options plus an automatic \" +\n    \"free-text input. The result maps each question to its answer (an array \" +\n    \"when multiSelect); a question absent from the result was skipped.\",\n  parametersSchema: ASK_USER_QUESTION_PARAMETERS_SCHEMA,\n  origin: \"sdk\",\n  annotations: { readOnlyHint: true },\n};\n\n/**\n * Built-in client tools to append to a dispatch's `clientTools[]` for the\n * given widget config: `ask_user_question` and `suggest_replies`; future\n * built-in local tools join here.\n *\n * Each tool is gated on BOTH of its feature flags: `expose` opts the tool\n * into the agent's catalog, and `enabled !== false` guarantees the widget can\n * actually render UI (and, for fire-and-forget tools, auto-resume) for it: * exposing a tool with its feature disabled would park the execution on a\n * generic tool bubble with no way to advance.\n */\nexport const builtInClientToolsForDispatch = (\n  config: AgentWidgetConfig | undefined,\n): ClientToolDefinition[] => {\n  const tools: ClientToolDefinition[] = [];\n  const ask = config?.features?.askUserQuestion;\n  if (ask?.expose === true && ask.enabled !== false) {\n    tools.push(ASK_USER_QUESTION_CLIENT_TOOL);\n  }\n  if (shouldExposeSuggestReplies(config)) {\n    tools.push(SUGGEST_REPLIES_CLIENT_TOOL);\n  }\n  return tools;\n};\n","import { AgentWidgetReasoning, AgentWidgetToolCall, AgentWidgetStreamParser, AgentWidgetStreamParserResult } from \"../types\";\nimport { parse as parsePartialJson, STR, OBJ } from \"partial-json\";\n\n/**\n * Unescapes JSON string escape sequences that LLMs often double-escape.\n * Converts literal \\n, \\r, \\t sequences to actual control characters.\n */\nconst unescapeJsonString = (str: string): string => {\n  return str\n    .replace(/\\\\n/g, '\\n')\n    .replace(/\\\\r/g, '\\r')\n    .replace(/\\\\t/g, '\\t')\n    .replace(/\\\\\"/g, '\"')\n    .replace(/\\\\\\\\/g, '\\\\');\n};\n\nexport const formatUnknownValue = (value: unknown): string => {\n  if (value === null) return \"null\";\n  if (value === undefined) return \"\";\n  if (typeof value === \"string\") return value;\n  if (typeof value === \"number\" || typeof value === \"boolean\") {\n    return String(value);\n  }\n  try {\n    return JSON.stringify(value, null, 2);\n  } catch (error) {\n    return String(value);\n  }\n};\n\nexport const formatReasoningDuration = (reasoning: AgentWidgetReasoning) => {\n  const end = reasoning.completedAt ?? Date.now();\n  const start = reasoning.startedAt ?? end;\n  const durationMs =\n    reasoning.durationMs !== undefined\n      ? reasoning.durationMs\n      : Math.max(0, end - start);\n  const seconds = durationMs / 1000;\n  if (seconds < 0.1) {\n    return \"Thought for <0.1 seconds\";\n  }\n  const formatted =\n    seconds >= 10\n      ? Math.round(seconds).toString()\n      : seconds.toFixed(1).replace(/\\.0$/, \"\");\n  return `Thought for ${formatted} seconds`;\n};\n\nexport const describeReasonStatus = (reasoning: AgentWidgetReasoning) => {\n  if (reasoning.status === \"complete\") return formatReasoningDuration(reasoning);\n  if (reasoning.status === \"pending\") return \"Waiting\";\n  return \"\";\n};\n\nexport const formatToolDuration = (tool: AgentWidgetToolCall) => {\n  const durationMs =\n    typeof tool.duration === \"number\"\n      ? tool.duration\n      : typeof tool.durationMs === \"number\"\n        ? tool.durationMs\n        : Math.max(\n            0,\n            (tool.completedAt ?? Date.now()) -\n              (tool.startedAt ?? tool.completedAt ?? Date.now())\n          );\n  const seconds = durationMs / 1000;\n  if (seconds < 0.1) {\n    return \"Used tool for <0.1 seconds\";\n  }\n  const formatted =\n    seconds >= 10\n      ? Math.round(seconds).toString()\n      : seconds.toFixed(1).replace(/\\.0$/, \"\");\n  return `Used tool for ${formatted} seconds`;\n};\n\nexport const describeToolStatus = (status: AgentWidgetToolCall[\"status\"]) => {\n  if (status === \"complete\") return \"\";\n  if (status === \"pending\") return \"Starting\";\n  return \"Running\";\n};\n\nexport const describeToolTitle = (tool: AgentWidgetToolCall) => {\n  if (tool.status === \"complete\") {\n    return formatToolDuration(tool);\n  }\n  return \"Using tool...\";\n};\n\n/**\n * Formats a millisecond duration as a short human-readable string.\n * Returns \"2.3s\", \"15s\", or \"<0.1s\".\n */\nexport const formatElapsedMs = (ms: number): string => {\n  const seconds = ms / 1000;\n  if (seconds < 0.1) return \"<0.1s\";\n  if (seconds >= 10) return `${Math.round(seconds)}s`;\n  return `${seconds.toFixed(1).replace(/\\.0$/, \"\")}s`;\n};\n\n/**\n * Computes the current elapsed time string for a tool call.\n */\nexport const computeToolElapsed = (tool: AgentWidgetToolCall): string => {\n  const durationMs =\n    typeof tool.duration === \"number\"\n      ? tool.duration\n      : typeof tool.durationMs === \"number\"\n        ? tool.durationMs\n        : Math.max(\n            0,\n            (tool.completedAt ?? Date.now()) -\n              (tool.startedAt ?? tool.completedAt ?? Date.now())\n          );\n  return formatElapsedMs(durationMs);\n};\n\n/**\n * Computes the current elapsed time string for a reasoning block.\n */\nexport const computeReasoningElapsed = (reasoning: AgentWidgetReasoning): string => {\n  const durationMs =\n    reasoning.durationMs !== undefined\n      ? reasoning.durationMs\n      : Math.max(\n          0,\n          (reasoning.completedAt ?? Date.now()) -\n            (reasoning.startedAt ?? reasoning.completedAt ?? Date.now())\n        );\n  return formatElapsedMs(durationMs);\n};\n\n/**\n * Resolves a text template with tool call placeholders.\n * Supported placeholders: {toolName}, {duration}\n * Returns the fallback if template is undefined.\n */\nexport const resolveToolHeaderText = (\n  tool: AgentWidgetToolCall,\n  template: string | undefined,\n  fallback: string\n): string => {\n  if (!template) return fallback;\n\n  const toolName = tool.name?.trim() || \"tool\";\n  const duration = computeToolElapsed(tool);\n\n  return template\n    .replace(/\\{toolName\\}/g, toolName)\n    .replace(/\\{duration\\}/g, duration);\n};\n\n/**\n * A segment of parsed template text with optional inline formatting.\n */\nexport interface TemplateSegment {\n  /** The text content (or \"{duration}\" for duration placeholders) */\n  text: string;\n  /** CSS modifier names to apply: \"dim\", \"bold\", \"italic\" */\n  styles: string[];\n  /** True when this segment represents a {duration} placeholder */\n  isDuration?: boolean;\n}\n\n/**\n * Parses a template string with inline formatting markers into segments.\n *\n * Supported markers (Markdown-like):\n * - `**text**` → bold\n * - `*text*`  → italic\n * - `~text~`  → dim / muted\n *\n * Placeholders `{toolName}` are resolved; `{duration}` is preserved as a\n * typed segment so the caller can render it as a live-updating DOM node.\n *\n * @example\n * parseFormattedTemplate(\"Finished {toolName} ~{duration}~\", \"Get Weather\")\n * // → [\n * //   { text: \"Finished Get Weather \", styles: [] },\n * //   { text: \"{duration}\", styles: [\"dim\"], isDuration: true }\n * // ]\n */\nexport const parseFormattedTemplate = (\n  template: string,\n  toolName: string\n): TemplateSegment[] => {\n  const resolved = template.replace(/\\{toolName\\}/g, toolName);\n  const segments: TemplateSegment[] = [];\n  // Order matters: ** must match before *\n  const regex = /\\*\\*(.+?)\\*\\*|\\*(.+?)\\*|~(.+?)~/g;\n\n  let lastIndex = 0;\n  let match;\n\n  while ((match = regex.exec(resolved)) !== null) {\n    if (match.index > lastIndex) {\n      pushSegments(segments, resolved.slice(lastIndex, match.index), []);\n    }\n\n    if (match[1] !== undefined) {\n      pushSegments(segments, match[1], [\"bold\"]);\n    } else if (match[2] !== undefined) {\n      pushSegments(segments, match[2], [\"italic\"]);\n    } else if (match[3] !== undefined) {\n      pushSegments(segments, match[3], [\"dim\"]);\n    }\n\n    lastIndex = match.index + match[0].length;\n  }\n\n  if (lastIndex < resolved.length) {\n    pushSegments(segments, resolved.slice(lastIndex), []);\n  }\n\n  return segments;\n};\n\n/** Splits text on {duration} and pushes typed segments. */\nconst pushSegments = (\n  segments: TemplateSegment[],\n  text: string,\n  styles: string[]\n): void => {\n  const parts = text.split(\"{duration}\");\n  for (let i = 0; i < parts.length; i++) {\n    if (parts[i]) {\n      segments.push({ text: parts[i], styles });\n    }\n    if (i < parts.length - 1) {\n      segments.push({ text: \"{duration}\", styles, isDuration: true });\n    }\n  }\n};\n\n/**\n * Creates a regex-based parser for extracting text from JSON streams.\n * This is a simpler alternative to schema-stream that uses regex to extract\n * the 'text' field incrementally as JSON streams in.\n * \n * This can be used as an alternative parser option.\n */\nconst createRegexJsonParserInternal = (): {\n  processChunk(accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null>;\n  getExtractedText(): string | null;\n  close?(): Promise<void>;\n} => {\n  let extractedText: string | null = null;\n  let processedLength = 0;\n  \n  // Regex-based extraction for incremental JSON parsing\n  const extractTextFromIncompleteJson = (jsonString: string): string | null => {\n    // Look for \"text\": \"value\" pattern, handling incomplete strings\n    // Match: \"text\": \" followed by any characters (including incomplete)\n    const textFieldRegex = /\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.|\")*?)\"/;\n    const match = jsonString.match(textFieldRegex);\n    \n    if (match && match[1]) {\n      // Unescape the string value\n      try {\n        // Replace escaped characters\n        let unescaped = match[1]\n          .replace(/\\\\n/g, '\\n')\n          .replace(/\\\\r/g, '\\r')\n          .replace(/\\\\t/g, '\\t')\n          .replace(/\\\\\"/g, '\"')\n          .replace(/\\\\\\\\/g, '\\\\');\n        return unescaped;\n      } catch {\n        return match[1];\n      }\n    }\n    \n    // Also try to match incomplete text field (text field that hasn't closed yet)\n    // Look for \"text\": \" followed by content that may not be closed\n    const incompleteTextFieldRegex = /\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)/;\n    const incompleteMatch = jsonString.match(incompleteTextFieldRegex);\n    \n    if (incompleteMatch && incompleteMatch[1]) {\n      // Unescape the partial string value\n      try {\n        let unescaped = incompleteMatch[1]\n          .replace(/\\\\n/g, '\\n')\n          .replace(/\\\\r/g, '\\r')\n          .replace(/\\\\t/g, '\\t')\n          .replace(/\\\\\"/g, '\"')\n          .replace(/\\\\\\\\/g, '\\\\');\n        return unescaped;\n      } catch {\n        return incompleteMatch[1];\n      }\n    }\n    \n    return null;\n  };\n  \n  return {\n    getExtractedText: () => extractedText,\n    processChunk: async (accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null> => {\n      // Skip if no new content\n      if (accumulatedContent.length <= processedLength) {\n        return extractedText !== null\n          ? { text: extractedText, raw: accumulatedContent }\n          : null;\n      }\n      \n      // Validate that the accumulated content looks like valid JSON\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n        return null;\n      }\n      \n      // Try to extract text field using regex\n      const extracted = extractTextFromIncompleteJson(accumulatedContent);\n      if (extracted !== null) {\n        extractedText = extracted;\n      }\n      \n      // Update processed length\n      processedLength = accumulatedContent.length;\n      \n      // Return both the extracted text and raw JSON\n      if (extractedText !== null) {\n        return {\n          text: extractedText,\n          raw: accumulatedContent\n        };\n      }\n\n      return null;\n    },\n    close: async () => {\n      // No cleanup needed for regex-based parser\n    }\n  };\n};\n\n/**\n * Extracts the text field from JSON (works with partial JSON during streaming).\n * For complete JSON, uses fast path. For incomplete JSON, returns null (use stateful parser in client.ts).\n * \n * @param jsonString - The JSON string (can be partial/incomplete during streaming)\n * @returns The extracted text value, or null if not found or invalid\n */\nexport const extractTextFromJson = (jsonString: string): string | null => {\n  try {\n    // Try to parse complete JSON first (fast path)\n    const parsed = JSON.parse(jsonString);\n    if (parsed && typeof parsed === \"object\" && typeof parsed.text === \"string\") {\n      return parsed.text;\n    }\n  } catch {\n    // For incomplete JSON, return null - use stateful parser in client.ts\n    return null;\n  }\n  return null;\n};\n\n/**\n * Plain text parser - passes through text as-is without any parsing.\n * This is the default parser.\n */\nexport const createPlainTextParser = (): AgentWidgetStreamParser => {\n  const parser: AgentWidgetStreamParser = {\n    processChunk: (_accumulatedContent: string): string | null => {\n      // Always return null to indicate this isn't a structured format\n      // Content will be displayed as plain text\n      return null;\n    },\n    getExtractedText: (): string | null => {\n      return null;\n    }\n  };\n  // Mark this as a plain text parser\n  (parser as any).__isPlainTextParser = true;\n  return parser;\n};\n\n/**\n * JSON parser using regex-based extraction.\n * Extracts the 'text' field from JSON responses using regex patterns.\n * This is a simpler regex-based alternative to createJsonStreamParser.\n * Less robust for complex/malformed JSON but has no external dependencies.\n */\nexport const createRegexJsonParser = (): AgentWidgetStreamParser => {\n  const regexParser = createRegexJsonParserInternal();\n  \n  return {\n    processChunk: async (accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null> => {\n      // Only process if it looks like JSON\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n        return null;\n      }\n      return regexParser.processChunk(accumulatedContent);\n    },\n    getExtractedText: regexParser.getExtractedText.bind(regexParser),\n    close: regexParser.close?.bind(regexParser)\n  };\n};\n\n/**\n * JSON stream parser using partial-json library.\n * Extracts the 'text' field from JSON responses using the partial-json library,\n * which is specifically designed for parsing incomplete JSON from LLMs.\n * This is the recommended parser as it's more robust than regex.\n * \n * Library: https://github.com/promplate/partial-json-parser-js\n */\nexport const createJsonStreamParser = (): AgentWidgetStreamParser => {\n  let extractedText: string | null = null;\n  let processedLength = 0;\n  \n  return {\n    getExtractedText: () => extractedText,\n    processChunk: (accumulatedContent: string): AgentWidgetStreamParserResult | string | null => {\n      // Validate that the accumulated content looks like JSON\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n        return null;\n      }\n      \n      // Skip if no new content\n      if (accumulatedContent.length <= processedLength) {\n        return extractedText !== null || extractedText === \"\"\n          ? { text: extractedText || \"\", raw: accumulatedContent }\n          : null;\n      }\n      \n      try {\n        // Parse partial JSON - allow partial strings and objects\n        // STR | OBJ allows incomplete strings and objects during streaming\n        const parsed = parsePartialJson(accumulatedContent, STR | OBJ);\n        \n        if (parsed && typeof parsed === \"object\") {\n          // Check for component directives - extract text if present for combined text+component\n          if (parsed.component && typeof parsed.component === \"string\") {\n            // For component directives, extract text if present, otherwise empty\n            extractedText = typeof parsed.text === \"string\" ? unescapeJsonString(parsed.text) : \"\";\n          }\n          // Check for form directives - these also don't have text fields\n          else if (parsed.type === \"init\" && parsed.form) {\n            // For form directives, return empty - they're handled by form postprocessor\n            extractedText = \"\";\n          }\n          // Extract text field if available\n          else if (typeof parsed.text === \"string\") {\n            extractedText = unescapeJsonString(parsed.text);\n          }\n        }\n      } catch (error) {\n        // If parsing fails completely, keep the last extracted text\n        // This can happen with very malformed JSON\n      }\n      \n      // Update processed length\n      processedLength = accumulatedContent.length;\n      \n      // Always return raw JSON for component/form directive detection\n      // Return empty string for text if it's a component/form directive\n      if (extractedText !== null) {\n        return {\n          text: extractedText,\n          raw: accumulatedContent\n        };\n      }\n\n      return null;\n    },\n    close: () => {\n      // No cleanup needed\n    }\n  };\n};\n\n/**\n * Flexible JSON stream parser that can extract text from various field names.\n * This parser looks for display text in multiple possible fields, making it\n * compatible with different JSON response formats.\n * \n * @param textExtractor Optional function to extract display text from parsed JSON.\n *                      If not provided, looks for common text fields.\n */\nexport const createFlexibleJsonStreamParser = (\n  textExtractor?: (parsed: any) => string | null\n): AgentWidgetStreamParser => {\n  let extractedText: string | null = null;\n  let processedLength = 0;\n  \n  // Default text extractor that handles common patterns\n  const defaultExtractor = (parsed: any): string | null => {\n    if (!parsed || typeof parsed !== \"object\") return null;\n\n    // Helper to safely extract and unescape text\n    const getText = (value: any): string | null => {\n      return typeof value === \"string\" ? unescapeJsonString(value) : null;\n    };\n\n    // Check for component directives - extract text if present for combined text+component\n    if (parsed.component && typeof parsed.component === \"string\") {\n      // For component directives, extract text if present, otherwise empty\n      return typeof parsed.text === \"string\" ? unescapeJsonString(parsed.text) : \"\";\n    }\n    \n    // Check for form directives - these also don't have text fields\n    if (parsed.type === \"init\" && parsed.form) {\n      // For form directives, return empty - they're handled by form postprocessor\n      return \"\";\n    }\n    \n    // Check for action-based text fields\n    if (parsed.action) {\n      switch (parsed.action) {\n        case 'nav_then_click':\n          return getText(parsed.on_load_text) || getText(parsed.text) || null;\n        case 'message':\n        case 'message_and_click':\n        case 'checkout':\n          return getText(parsed.text) || null;\n        default:\n          return getText(parsed.text) || getText(parsed.display_text) || getText(parsed.message) || null;\n      }\n    }\n    \n    // Fallback to common text field names\n    return getText(parsed.text) || getText(parsed.display_text) || getText(parsed.message) || getText(parsed.content) || null;\n  };\n  \n  const extractText = textExtractor || defaultExtractor;\n  \n  return {\n    getExtractedText: () => extractedText,\n    processChunk: (accumulatedContent: string): AgentWidgetStreamParserResult | string | null => {\n      // Validate that the accumulated content looks like JSON\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {\n        return null;\n      }\n      \n      // Skip if no new content\n      if (accumulatedContent.length <= processedLength) {\n        return extractedText !== null\n          ? { text: extractedText, raw: accumulatedContent }\n          : null;\n      }\n      \n      try {\n        // Parse partial JSON - allow partial strings and objects\n        // STR | OBJ allows incomplete strings and objects during streaming\n        const parsed = parsePartialJson(accumulatedContent, STR | OBJ);\n        \n        // Extract text using the provided or default extractor\n        const newText = extractText(parsed);\n        if (newText !== null) {\n          extractedText = newText;\n        }\n      } catch (error) {\n        // If parsing fails completely, keep the last extracted text\n        // This can happen with very malformed JSON\n      }\n      \n      // Update processed length\n      processedLength = accumulatedContent.length;\n      \n      // Always return the raw JSON for action parsing and component detection\n      // Text may be null or empty for component/form directives, that's ok\n      return {\n        text: extractedText || \"\",\n        raw: accumulatedContent\n      };\n    },\n    close: () => {\n      // No cleanup needed\n    }\n  };\n};\n\n/**\n * XML stream parser.\n * Extracts text from <text>...</text> tags in XML responses.\n */\nexport const createXmlParser = (): AgentWidgetStreamParser => {\n  let extractedText: string | null = null;\n  \n  return {\n    processChunk: (accumulatedContent: string): AgentWidgetStreamParserResult | string | null => {\n      // Return null if not XML format\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith('<')) {\n        return null;\n      }\n      \n      // Extract text from <text>...</text> tags\n      // Handle both <text>content</text> and <text attr=\"value\">content</text>\n      const match = accumulatedContent.match(/<text[^>]*>([\\s\\S]*?)<\\/text>/);\n      if (match && match[1]) {\n        extractedText = match[1];\n        // For XML, we typically don't need the raw content for middleware\n        // but we can include it for consistency\n        return { text: extractedText, raw: accumulatedContent };\n      }\n      \n      return null;\n    },\n    getExtractedText: (): string | null => {\n      return extractedText;\n    }\n  };\n};\n\n\n\n\n\n\n\n","{\n  \"name\": \"@runtypelabs/persona\",\n  \"version\": \"4.8.0\",\n  \"description\": \"Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.cjs\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.cjs\"\n    },\n    \"./theme-reference\": {\n      \"types\": \"./dist/theme-reference.d.ts\",\n      \"import\": \"./dist/theme-reference.js\",\n      \"require\": \"./dist/theme-reference.cjs\"\n    },\n    \"./codegen\": {\n      \"types\": \"./dist/codegen.d.ts\",\n      \"import\": \"./dist/codegen.js\",\n      \"require\": \"./dist/codegen.cjs\"\n    },\n    \"./theme-editor\": {\n      \"types\": \"./dist/theme-editor.d.ts\",\n      \"import\": \"./dist/theme-editor.js\",\n      \"require\": \"./dist/theme-editor.cjs\"\n    },\n    \"./theme-editor/preview\": {\n      \"types\": \"./dist/theme-editor-preview.d.ts\",\n      \"import\": \"./dist/theme-editor-preview.js\",\n      \"require\": \"./dist/theme-editor-preview.cjs\"\n    },\n    \"./testing\": {\n      \"types\": \"./dist/testing.d.ts\",\n      \"import\": \"./dist/testing.js\",\n      \"require\": \"./dist/testing.cjs\"\n    },\n    \"./smart-dom-reader\": {\n      \"types\": \"./dist/smart-dom-reader.d.ts\",\n      \"import\": \"./dist/smart-dom-reader.js\",\n      \"require\": \"./dist/smart-dom-reader.cjs\"\n    },\n    \"./voice-worklet-player\": {\n      \"types\": \"./dist/voice-worklet-player.d.ts\",\n      \"import\": \"./dist/voice-worklet-player.js\",\n      \"require\": \"./dist/voice-worklet-player.cjs\"\n    },\n    \"./plugin-kit\": {\n      \"types\": \"./dist/plugin-kit.d.ts\",\n      \"import\": \"./dist/plugin-kit.js\",\n      \"require\": \"./dist/plugin-kit.cjs\"\n    },\n    \"./animations/glyph-cycle\": {\n      \"types\": \"./dist/animations/glyph-cycle.d.ts\",\n      \"import\": \"./dist/animations/glyph-cycle.js\",\n      \"require\": \"./dist/animations/glyph-cycle.cjs\"\n    },\n    \"./animations/wipe\": {\n      \"types\": \"./dist/animations/wipe.d.ts\",\n      \"import\": \"./dist/animations/wipe.js\",\n      \"require\": \"./dist/animations/wipe.cjs\"\n    },\n    \"./widget.css\": \"./dist/widget.css\"\n  },\n  \"files\": [\n    \"dist\",\n    \"src\"\n  ],\n  \"scripts\": {\n    \"build\": \"rimraf dist && pnpm run build:styles && pnpm run build:markdown-parsers && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:webmcp-polyfill && pnpm run build:runtype-tts && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:theme-editor-preview && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:voice-worklet-player && pnpm run build:plugin-kit && pnpm run build:animations && pnpm run check:dist-vite8\",\n    \"check:dist-vite8\": \"node scripts/check-dist-no-in-for-init.mjs\",\n    \"build:markdown-parsers\": \"tsup --config tsup.markdown-parsers.config.ts\",\n    \"build:plugin-kit\": \"tsup src/plugin-kit.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting\",\n    \"build:theme-editor\": \"tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting\",\n    \"build:theme-editor-preview\": \"tsup src/theme-editor-preview.ts --format esm,cjs --minify --dts --out-dir dist\",\n    \"build:testing\": \"tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting\",\n    \"build:smart-dom-reader\": \"tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting\",\n    \"build:voice-worklet-player\": \"tsup src/voice-worklet-player.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting\",\n    \"build:animations\": \"tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --minify --dts --out-dir dist/animations --no-splitting\",\n    \"build:theme-ref\": \"tsup src/theme-reference.ts --format esm,cjs --minify --dts\",\n    \"build:codegen\": \"tsup src/codegen.ts --format esm,cjs --minify --dts\",\n    \"build:styles\": \"node scripts/build-styles.mjs\",\n    \"build:client\": \"tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \\\".css=text\\\" && tsup --config tsup.global.config.ts && node -e \\\"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\\\"\",\n    \"build:webmcp-polyfill\": \"tsup --config tsup.webmcp-polyfill.config.ts\",\n    \"build:runtype-tts\": \"tsup --config tsup.runtype-tts.config.ts\",\n    \"build:installer\": \"tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting\",\n    \"build:launcher\": \"tsup src/launcher-global.ts --format iife --global-name AgentWidgetLauncher --minify --sourcemap --splitting false --out-dir dist && node -e \\\"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/launcher-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/launcher'+ext);}\\\"\",\n    \"lint\": \"eslint . --ext .ts\",\n    \"typecheck\": \"pnpm run check:runtype-types && tsc --noEmit\",\n    \"test\": \"vitest\",\n    \"test:ui\": \"vitest --ui\",\n    \"test:run\": \"vitest run\",\n    \"size\": \"size-limit\",\n    \"fetch:runtype-openapi\": \"node scripts/fetch-runtype-openapi.mjs\",\n    \"generate:runtype-types\": \"pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs\",\n    \"check:runtype-types\": \"pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs --check\"\n  },\n  \"dependencies\": {\n    \"@mcp-b/webmcp-polyfill\": \"^3.0.0\",\n    \"dompurify\": \"^3.4.10\",\n    \"idiomorph\": \"^0.7.4\",\n    \"lucide\": \"^1.18.0\",\n    \"marked\": \"^12.0.2\",\n    \"partial-json\": \"^0.1.7\"\n  },\n  \"devDependencies\": {\n    \"@size-limit/file\": \"^12.1.0\",\n    \"acorn\": \"^8.15.0\",\n    \"@types/node\": \"^20.12.7\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.0.0\",\n    \"@typescript-eslint/parser\": \"^7.0.0\",\n    \"@vitest/ui\": \"^4.0.9\",\n    \"esbuild\": \"^0.21.5\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"fake-indexeddb\": \"^6.2.5\",\n    \"rimraf\": \"^5.0.5\",\n    \"size-limit\": \"^12.1.0\",\n    \"tsup\": \"^8.0.1\",\n    \"typescript\": \"^5.4.5\",\n    \"vitest\": \"^4.0.9\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"author\": \"Runtype\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"ai\",\n    \"chat\",\n    \"widget\",\n    \"streaming\",\n    \"typescript\",\n    \"persona\",\n    \"agent\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/runtypelabs/persona.git\",\n    \"directory\": \"packages/widget\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/runtypelabs/persona/issues\"\n  },\n  \"homepage\": \"https://github.com/runtypelabs/persona/tree/main/packages/widget#readme\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n","// Named import (not default) so esbuild tree-shakes the JSON module down to\n// the one field we read: a default import inlines the entire package.json\n// (~4.8 kB minified) into every bundle.\nimport { version } from \"../package.json\";\n\n/**\n * The current version of the @runtypelabs/persona package.\n * This is automatically derived from package.json.\n */\nexport const VERSION = version;\n","import {\n  AgentWidgetConfig,\n  AgentWidgetMessage,\n  AgentWidgetEvent,\n  AgentWidgetStreamParser,\n  AgentWidgetStreamParserResult,\n  AgentWidgetContextProvider,\n  AgentWidgetRequestMiddleware,\n  AgentWidgetRequestPayload,\n  AgentWidgetAgentRequestPayload,\n  AgentWidgetCustomFetch,\n  AgentWidgetSSEEventParser,\n  AgentWidgetHeadersFunction,\n  AgentWidgetSSEEventResult as _AgentWidgetSSEEventResult,\n  AgentExecutionState,\n  StopReasonKind,\n  ClientSession,\n  ClientInitResponse,\n  ClientChatRequest,\n  ClientToolDefinition,\n  ClientFeedbackRequest,\n  ClientFeedbackType,\n  PersonaArtifactKind,\n  PersonaArtifactFileMeta,\n  ContentPart,\n  WebMcpConfirmHandler\n} from \"./types\";\nimport { WebMcpBridge, computeClientToolsFingerprint, isWebMcpToolName } from \"./webmcp-bridge\";\nimport {\n  buildArtifactRefRawContent,\n  resolveArtifactDisplayMode\n} from \"./utils/artifact-display\";\nimport { resolveTarget } from \"./utils/target\";\nimport { builtInClientToolsForDispatch } from \"./ask-user-question-tool\";\nimport {\n  extractTextFromJson,\n  createPlainTextParser,\n  createJsonStreamParser,\n  createRegexJsonParser,\n  createXmlParser\n} from \"./utils/formatting\";\nimport { VERSION } from \"./version\";\n// artifactsSidebarEnabled is used in ui.ts to gate the sidebar pane rendering;\n// artifact events are always processed here regardless of config.\n\ntype DispatchOptions = {\n  messages: AgentWidgetMessage[];\n  signal?: AbortSignal;\n  /** Pre-generated ID for the expected assistant response (for feedback tracking) */\n  assistantMessageId?: string;\n};\n\ntype SSEHandler = (event: AgentWidgetEvent) => void;\n\nconst DEFAULT_ENDPOINT = \"https://api.runtype.com/v1/dispatch\";\nconst DEFAULT_CLIENT_API_BASE = \"https://api.runtype.com\";\n\n/**\n * Derive a download filename for `agent_media` parts that are delivered\n * without one. Maps a few well-known MIME types to friendly extensions and\n * falls back to `attachment.<subtype>` (or just `attachment` for opaque\n * types like `application/octet-stream`).\n */\nfunction filenameFromMediaType(mediaType: string): string {\n  // MIME types are case-insensitive (RFC 7231); compare against a lowercased\n  // copy so callers that pass mixed casing still hit the friendly extensions.\n  const lower = mediaType.toLowerCase();\n  const knownExtensions: Record<string, string> = {\n    \"application/pdf\": \"pdf\",\n    \"application/json\": \"json\",\n    \"application/zip\": \"zip\",\n    \"text/plain\": \"txt\",\n    \"text/csv\": \"csv\",\n    \"text/markdown\": \"md\"\n  };\n  const ext = knownExtensions[lower];\n  if (ext) return `attachment.${ext}`;\n  const slash = lower.indexOf(\"/\");\n  if (slash > 0) {\n    const subtype = lower.slice(slash + 1).split(\";\")[0]?.trim() ?? \"\";\n    if (subtype && subtype !== \"octet-stream\" && /^[a-z0-9.+-]+$/i.test(subtype)) {\n      return `attachment.${subtype}`;\n    }\n  }\n  return \"attachment\";\n}\n\n/**\n * Check if a message has valid (non-empty) content for sending to the API.\n * Filters out messages with empty content that would cause validation errors.\n *\n */\nconst hasValidContent = (message: AgentWidgetMessage): boolean => {\n  // Check contentParts (multi-modal content)\n  if (message.contentParts && message.contentParts.length > 0) {\n    return true;\n  }\n  // Check llmContent (explicit LLM content)\n  if (message.llmContent && message.llmContent.trim().length > 0) {\n    return true;\n  }\n  // Check rawContent (structured parser output)\n  if (message.rawContent && message.rawContent.trim().length > 0) {\n    return true;\n  }\n  // Check content (display content)\n  if (message.content && message.content.trim().length > 0) {\n    return true;\n  }\n  return false;\n};\n\n/**\n * Maps parserType string to the corresponding parser factory function\n */\nfunction getParserFromType(parserType?: \"plain\" | \"json\" | \"regex-json\" | \"xml\"): () => AgentWidgetStreamParser {\n  switch (parserType) {\n    case \"json\":\n      return createJsonStreamParser;\n    case \"regex-json\":\n      return createRegexJsonParser;\n    case \"xml\":\n      return createXmlParser;\n    case \"plain\":\n    default:\n      return createPlainTextParser;\n  }\n}\n\nexport type SSEEventCallback = (eventType: string, payload: unknown) => void;\n\nconst looksStructured = (value: string) =>\n  value.startsWith(\"{\") || value.startsWith(\"[\") || value.startsWith(\"<\");\n\n/**\n * Choose the best content source for sealed-segment reconciliation.\n * Prefers the final structured payload from step_complete when the raw\n * buffer is only a partial/unparseable prefix of the same structured format.\n */\nexport function preferFinalStructuredContent(\n  rawBuffer: string | undefined,\n  finalString: string\n): string {\n  if (!rawBuffer) return finalString;\n\n  const rawTrimmed = rawBuffer.trim();\n  const finalTrimmed = finalString.trim();\n  if (rawTrimmed.length === 0) return finalString;\n  if (finalTrimmed.length === 0) return rawBuffer;\n\n  const rawLooksStructured = looksStructured(rawTrimmed);\n  const finalLooksStructured = looksStructured(finalTrimmed);\n\n  if (!finalLooksStructured) return rawBuffer;\n  if (!rawLooksStructured) return finalString;\n  if (finalTrimmed === rawTrimmed) return finalString;\n  if (finalTrimmed.startsWith(rawTrimmed)) return finalString;\n\n  const rawJsonText = extractTextFromJson(rawBuffer);\n  const finalJsonText = extractTextFromJson(finalString);\n  if (finalJsonText !== null && rawJsonText === null) return finalString;\n\n  return rawBuffer;\n}\n\nexport class AgentWidgetClient {\n  private readonly apiUrl: string;\n  private readonly headers: Record<string, string>;\n  private readonly debug: boolean;\n  private readonly createStreamParser: () => AgentWidgetStreamParser;\n  private readonly contextProviders: AgentWidgetContextProvider[];\n  private readonly requestMiddleware?: AgentWidgetRequestMiddleware;\n  private readonly customFetch?: AgentWidgetCustomFetch;\n  private readonly parseSSEEvent?: AgentWidgetSSEEventParser;\n  private readonly getHeaders?: AgentWidgetHeadersFunction;\n  private onSSEEvent?: SSEEventCallback;\n  \n  // Client token mode properties\n  private clientSession: ClientSession | null = null;\n  private sessionInitPromise: Promise<ClientSession> | null = null;\n\n  // Diff-only / send-once WebMCP tool dispatch (client-token mode ONLY).\n  // Fingerprint of the clientTools[] last *sent in full* and confirmed by a\n  // successful stream start; null => the next client-token turn sends the full\n  // array. Paired with the sessionId it was sent under so a session change\n  // (silent re-init / expiry) forces a fresh full send.\n  private lastSentClientToolsFingerprint: string | null = null;\n  private clientToolsFingerprintSessionId: string | null = null;\n  // Session under which a non-empty clientTools[] was last committed and not\n  // yet confirmed cleared server-side. Distinct from the fingerprint above:\n  // an empty-tool chat turn commits a null fingerprint, but OMITTING the\n  // fields on chat doesn't clear the tools persisted for a still-paused\n  // execution — so a later resume with an empty registry must still send the\n  // explicit `clientTools: []` replace. Reset only by an explicit [] replace,\n  // a session change, or a conversation reset.\n  private sentNonEmptyClientToolsSessionId: string | null = null;\n\n  // WebMCP: page-discovered tool consumption (see ./webmcp-bridge).\n  // Constructed lazily: null when `config.webmcp?.enabled !== true`.\n  private readonly webMcpBridge: WebMcpBridge | null;\n\n  constructor(private config: AgentWidgetConfig = {}) {\n    if (config.target && (config.agentId || config.flowId || config.agent)) {\n      throw new Error(\n        \"[Persona] `target` is mutually exclusive with `agentId`, `flowId`, and `agent`. Set only one routing field.\",\n      );\n    }\n    this.apiUrl = config.apiUrl ?? DEFAULT_ENDPOINT;\n    this.headers = {\n      \"Content-Type\": \"application/json\",\n      \"X-Persona-Version\": VERSION,\n      ...config.headers\n    };\n    this.debug = Boolean(config.debug);\n    // Use custom stream parser if provided, otherwise use parserType, or fall back to plain text parser\n    this.createStreamParser = config.streamParser ?? getParserFromType(config.parserType);\n    this.contextProviders = config.contextProviders ?? [];\n    this.requestMiddleware = config.requestMiddleware;\n    this.customFetch = config.customFetch;\n    this.parseSSEEvent = config.parseSSEEvent;\n    this.getHeaders = config.getHeaders;\n    this.webMcpBridge =\n      config.webmcp?.enabled === true ? new WebMcpBridge(config.webmcp) : null;\n  }\n\n  /**\n   * Refresh config in place WITHOUT tearing down the live connection or the\n   * WebMCP bridge. `AgentWidgetSession.updateConfig` calls this when only\n   * connection-irrelevant fields changed (theme, copy, layout, suggestions, …),\n   * so a UI update that lands mid-turn: e.g. a `webmcp:*` tool restyling the\n   * widget while the agent's turn is still streaming: doesn't abandon the\n   * in-flight stream/resume. Connection or request-shaping changes (apiUrl,\n   * clientToken, webmcp, headers, parser, …) take the full client rebuild path\n   * in the session instead, which is the only place the bridge is recreated.\n   *\n   * Only the live-read `config` is refreshed (e.g. `iterationDisplay`); the\n   * constructor-derived request-shaping fields (apiUrl, headers, parser,\n   * contextProviders, middleware, …) are left untouched because the session\n   * routes any change to those down the full-rebuild path instead, so they are\n   * guaranteed unchanged here. The `webMcpBridge` instance and its\n   * installed-polyfill memo are deliberately preserved, which keeps any\n   * in-flight resolve alive.\n   */\n  public updateConfig(next: AgentWidgetConfig): void {\n    this.config = next;\n  }\n\n  /**\n   * Set callback for capturing raw SSE events\n   */\n  public setSSEEventCallback(callback: SSEEventCallback): void {\n    this.onSSEEvent = callback;\n  }\n\n  /**\n   * WebMCP: wire (or replace) the confirm-bubble handler. Called from\n   * `ui.ts` once the widget panel is built and the approval-bubble\n   * chrome is ready to render.\n   */\n  public setWebMcpConfirmHandler(handler: WebMcpConfirmHandler | null): void {\n    this.webMcpBridge?.setConfirmHandler(handler);\n  }\n\n  /**\n   * WebMCP: `true` when the bridge installed the polyfill and can both\n   * snapshot the page registry and execute returned `webmcp:*` tool calls.\n   * `false` for any guard miss (no `document.modelContext`, polyfill not yet\n   * installed, or `config.webmcp.enabled` not set).\n   */\n  public isWebMcpOperational(): boolean {\n    return this.webMcpBridge?.isOperational() === true;\n  }\n\n  /**\n   * WebMCP: execute a returned `webmcp:<name>` tool call against the page's\n   * registry and return the normalized MCP-shaped result for `/resume`. The\n   * bridge handles confirm-bubble gating, the 30s timeout, error\n   * normalization, and `signal`-driven abort: callers never see throws.\n   *\n   * Returns `null` when WebMCP is not enabled on this client (signal to the\n   * session that it should fall back to the legacy local-tool resume path,\n   * if any).\n   */\n  public executeWebMcpToolCall(\n    wireToolName: string,\n    args: unknown,\n    signal?: AbortSignal,\n  ): Promise<import(\"./types\").WebMcpToolResult> | null {\n    if (!this.webMcpBridge) return null;\n    return this.webMcpBridge.executeToolCall(wireToolName, args, signal);\n  }\n\n  /**\n   * Get the current SSE event callback (used to preserve across client recreation)\n   */\n  public getSSEEventCallback(): SSEEventCallback | undefined {\n    return this.onSSEEvent;\n  }\n\n  /**\n   * Check if running in client token mode\n   */\n  public isClientTokenMode(): boolean {\n    return !!this.config.clientToken;\n  }\n\n  /**\n   * Resolve the effective backend routing for the current config. Combines the\n   * explicit `agentId`/`flowId` fields with the normalized `target` string\n   * (resolved via `resolveTarget`). Computed on demand so it stays correct\n   * across `update()`; the `target`/explicit-field conflict is rejected in the\n   * constructor, so at most one source is set here.\n   */\n  private routing(): {\n    agentId?: string;\n    flowId?: string;\n    targetPayload?: Record<string, unknown>;\n  } {\n    const { agentId, flowId, target, targetProviders } = this.config;\n    if (!target) {\n      return { agentId, flowId };\n    }\n    const resolved = resolveTarget(target, targetProviders);\n    if (resolved.kind === \"agentId\") return { agentId: resolved.agentId };\n    if (resolved.kind === \"flowId\") return { flowId: resolved.flowId };\n    return { targetPayload: resolved.payload };\n  }\n\n  /**\n   * Check if operating in agent execution mode\n   */\n  public isAgentMode(): boolean {\n    return !!(this.config.agent || this.routing().agentId);\n  }\n\n  /**\n   * Get the appropriate API URL based on mode\n   */\n  private getClientApiUrl(endpoint: 'init' | 'chat' | 'resume'): string {\n    const baseUrl = this.config.apiUrl?.replace(/\\/+$/, '').replace(/\\/v1\\/dispatch$/, '') || DEFAULT_CLIENT_API_BASE;\n    return `${baseUrl}/v1/client/${endpoint}`;\n  }\n\n  /**\n   * Get the current client session (if any)\n   */\n  public getClientSession(): ClientSession | null {\n    return this.clientSession;\n  }\n\n  /**\n   * Initialize session for client token mode.\n   * Called automatically on first message if not already initialized.\n   */\n  public async initSession(): Promise<ClientSession> {\n    if (!this.isClientTokenMode()) {\n      throw new Error('initSession() only available in client token mode');\n    }\n\n    // Return existing session if valid\n    if (this.clientSession && new Date() < this.clientSession.expiresAt) {\n      return this.clientSession;\n    }\n\n    // Deduplicate concurrent init calls\n    if (this.sessionInitPromise) {\n      return this.sessionInitPromise;\n    }\n\n    this.sessionInitPromise = this._doInitSession();\n    try {\n      const session = await this.sessionInitPromise;\n      this.clientSession = session;\n      // A freshly-minted session must resend the full WebMCP tool list on its\n      // next turn: drop any diff-only fingerprint cached under a prior session,\n      // so we never claim \"unchanged\" against a session the server didn't store\n      // the set under. (Belt-and-suspenders with the sessionId comparison in the\n      // send decision and the server's 409 resend signal.)\n      this.resetClientToolsFingerprint();\n      this.config.onSessionInit?.(session);\n      return session;\n    } finally {\n      this.sessionInitPromise = null;\n    }\n  }\n\n  private async _doInitSession(): Promise<ClientSession> {\n    // Get stored session_id if available (for session resumption)\n    const storedSessionId = this.config.getStoredSessionId?.() || null;\n    \n    const routed = this.routing();\n    const sessionTargetId = routed.agentId ?? routed.flowId;\n    const requestBody: Record<string, unknown> = {\n      token: this.config.clientToken,\n      ...(sessionTargetId && { flowId: sessionTargetId }),\n      ...(storedSessionId && { sessionId: storedSessionId }),\n    };\n\n    const response = await fetch(this.getClientApiUrl('init'), {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'X-Persona-Version': VERSION,\n      },\n      body: JSON.stringify(requestBody),\n    });\n\n    if (!response.ok) {\n      const error = await response.json().catch(() => ({ error: 'Session initialization failed' }));\n      if (response.status === 401) {\n        throw new Error(`Invalid client token: ${error.hint || error.error}`);\n      }\n      if (response.status === 403) {\n        throw new Error(`Origin not allowed: ${error.hint || error.error}`);\n      }\n      throw new Error(error.error || 'Failed to initialize session');\n    }\n\n    const data: ClientInitResponse = await response.json();\n\n    // Store the new sessionId for future resumption\n    if (this.config.setStoredSessionId) {\n      this.config.setStoredSessionId(data.sessionId);\n    }\n\n    return {\n      sessionId: data.sessionId,\n      expiresAt: new Date(data.expiresAt),\n      flow: data.flow,\n      config: {\n        welcomeMessage: data.config.welcomeMessage,\n        placeholder: data.config.placeholder,\n        theme: data.config.theme,\n      },\n    };\n  }\n\n  /**\n   * Clear the current client session\n   */\n  public clearClientSession(): void {\n    this.clientSession = null;\n    this.sessionInitPromise = null;\n    this.resetClientToolsFingerprint();\n  }\n\n  /**\n   * Forget the diff-only WebMCP tool fingerprint so the next client-token turn\n   * resends the full `clientTools[]`. Called when the session is cleared and\n   * when the conversation is reset (`WidgetSession.clearMessages`).\n   */\n  public resetClientToolsFingerprint(): void {\n    this.lastSentClientToolsFingerprint = null;\n    this.clientToolsFingerprintSessionId = null;\n    this.sentNonEmptyClientToolsSessionId = null;\n  }\n\n  /**\n   * Get the feedback API URL\n   */\n  private getFeedbackApiUrl(): string {\n    const baseUrl = this.config.apiUrl?.replace(/\\/+$/, '').replace(/\\/v1\\/dispatch$/, '') || DEFAULT_CLIENT_API_BASE;\n    return `${baseUrl}/v1/client/feedback`;\n  }\n\n  /**\n   * Send feedback for a message (client token mode only).\n   * Supports upvote, downvote, copy, csat, and nps feedback types.\n   * \n   * @param feedback - The feedback request payload\n   * @returns Promise that resolves when feedback is sent successfully\n   * @throws Error if not in client token mode or if session is invalid\n   * \n   * @example\n   * ```typescript\n   * // Message feedback (upvote/downvote/copy)\n   * await client.sendFeedback({\n   *   sessionId: sessionId,\n   *   messageId: messageId,\n   *   type: 'upvote'\n   * });\n   *\n   * // CSAT feedback (1-5 rating)\n   * await client.sendFeedback({\n   *   sessionId: sessionId,\n   *   type: 'csat',\n   *   rating: 5,\n   *   comment: 'Great experience!'\n   * });\n   *\n   * // NPS feedback (0-10 rating)\n   * await client.sendFeedback({\n   *   sessionId: sessionId,\n   *   type: 'nps',\n   *   rating: 9\n   * });\n   * ```\n   */\n  public async sendFeedback(feedback: ClientFeedbackRequest): Promise<void> {\n    if (!this.isClientTokenMode()) {\n      throw new Error('sendFeedback() only available in client token mode');\n    }\n\n    const session = this.getClientSession();\n    if (!session) {\n      throw new Error('No active session. Please initialize session first.');\n    }\n\n    // Validate messageId is provided for message-level feedback types\n    const messageFeedbackTypes: ClientFeedbackType[] = ['upvote', 'downvote', 'copy'];\n    if (messageFeedbackTypes.includes(feedback.type) && !feedback.messageId) {\n      throw new Error(`messageId is required for ${feedback.type} feedback type`);\n    }\n\n    // Validate rating is provided for csat/nps feedback types\n    if (feedback.type === 'csat') {\n      if (feedback.rating === undefined || feedback.rating < 1 || feedback.rating > 5) {\n        throw new Error('CSAT rating must be between 1 and 5');\n      }\n    }\n    if (feedback.type === 'nps') {\n      if (feedback.rating === undefined || feedback.rating < 0 || feedback.rating > 10) {\n        throw new Error('NPS rating must be between 0 and 10');\n      }\n    }\n\n    if (this.debug) {\n      // eslint-disable-next-line no-console\n      console.debug(\"[AgentWidgetClient] sending feedback\", feedback);\n    }\n\n    // Scope the feedback request to the caller's client token, sourced the same\n    // way as the chat/init requests. sendFeedback is client-token-mode only\n    // (guarded above), so clientToken is always present here and an API key can\n    // never leak into the body. Left undefined only when the embed has none.\n    const requestBody = {\n      ...feedback,\n      ...(this.config.clientToken && { token: this.config.clientToken }),\n    };\n\n    const response = await fetch(this.getFeedbackApiUrl(), {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'X-Persona-Version': VERSION,\n      },\n      body: JSON.stringify(requestBody),\n    });\n\n    if (!response.ok) {\n      const errorData = await response.json().catch(() => ({ error: 'Feedback submission failed' }));\n      \n      if (response.status === 401) {\n        this.clientSession = null;\n        this.config.onSessionExpired?.();\n        throw new Error('Session expired. Please refresh to continue.');\n      }\n      \n      throw new Error(errorData.error || 'Failed to submit feedback');\n    }\n  }\n\n  /**\n   * Submit message feedback (upvote, downvote, or copy).\n   * Convenience method for sendFeedback with message-level feedback.\n   * \n   * @param messageId - The ID of the message to provide feedback for\n   * @param type - The feedback type: 'upvote', 'downvote', or 'copy'\n   */\n  public async submitMessageFeedback(\n    messageId: string, \n    type: 'upvote' | 'downvote' | 'copy'\n  ): Promise<void> {\n    const session = this.getClientSession();\n    if (!session) {\n      throw new Error('No active session. Please initialize session first.');\n    }\n\n    return this.sendFeedback({\n      sessionId: session.sessionId,\n      messageId: messageId,\n      type,\n    });\n  }\n\n  /**\n   * Submit CSAT (Customer Satisfaction) feedback.\n   * Convenience method for sendFeedback with CSAT feedback.\n   *\n   * @param rating - Rating from 1 to 5\n   * @param comment - Optional comment\n   */\n  public async submitCSATFeedback(rating: number, comment?: string): Promise<void> {\n    const session = this.getClientSession();\n    if (!session) {\n      throw new Error('No active session. Please initialize session first.');\n    }\n\n    return this.sendFeedback({\n      sessionId: session.sessionId,\n      type: 'csat',\n      rating,\n      comment,\n    });\n  }\n\n  /**\n   * Submit NPS (Net Promoter Score) feedback.\n   * Convenience method for sendFeedback with NPS feedback.\n   *\n   * @param rating - Rating from 0 to 10\n   * @param comment - Optional comment\n   */\n  public async submitNPSFeedback(rating: number, comment?: string): Promise<void> {\n    const session = this.getClientSession();\n    if (!session) {\n      throw new Error('No active session. Please initialize session first.');\n    }\n\n    return this.sendFeedback({\n      sessionId: session.sessionId,\n      type: 'nps',\n      rating,\n      comment,\n    });\n  }\n\n  /**\n   * Send a message - handles both proxy and client token modes\n   */\n  public async dispatch(options: DispatchOptions, onEvent: SSEHandler) {\n    options.signal?.throwIfAborted();\n    if (this.isClientTokenMode()) {\n      return this.dispatchClientToken(options, onEvent);\n    }\n    if (this.isAgentMode()) {\n      return this.dispatchAgent(options, onEvent);\n    }\n    return this.dispatchProxy(options, onEvent);\n  }\n\n  /**\n   * Client token mode dispatch\n   */\n  private async dispatchClientToken(options: DispatchOptions, onEvent: SSEHandler) {\n    onEvent({ type: \"status\", status: \"connecting\" });\n\n    try {\n      // Ensure session is initialized\n      const session = await this.initSession();\n\n      // Check if session is about to expire (within 1 minute)\n      if (new Date() >= new Date(session.expiresAt.getTime() - 60000)) {\n        // Session expired or expiring soon\n        this.clearClientSession();\n        this.config.onSessionExpired?.();\n        const error = new Error('Session expired. Please refresh to continue.');\n        onEvent({ type: \"error\", error });\n        throw error;\n      }\n\n      // Build the standard payload to get context/metadata from middleware\n      const basePayload = await this.buildPayload(options.messages);\n\n      // Build the chat request payload with message IDs for feedback tracking\n      // Filter out sessionId from metadata if present (it's only for local storage)\n      const sanitizedMetadata = basePayload.metadata\n        ? Object.fromEntries(\n            Object.entries(basePayload.metadata).filter(([key]) => key !== 'sessionId' && key !== 'session_id')\n          )\n        : undefined;\n      \n      // Common (tools-independent) fields for the chat request.\n      const baseChatRequest: Omit<ClientChatRequest, 'clientTools' | 'clientToolsFingerprint'> = {\n        sessionId: session.sessionId,\n        // Filter out messages with empty content to prevent validation errors\n        messages: options.messages.filter(hasValidContent).map(m => ({\n          id: m.id, // Include message ID for tracking\n          role: m.role,\n          // Priority: contentParts (multi-modal) > llmContent (explicit LLM content) > rawContent (structured parsers) > content (display)\n          content: m.contentParts ?? m.llmContent ?? m.rawContent ?? m.content,\n        })),\n        // Include pre-generated assistant message ID if provided\n        ...(options.assistantMessageId && { assistantMessageId: options.assistantMessageId }),\n        // Include metadata/context from middleware if present (excluding sessionId)\n        ...(sanitizedMetadata && Object.keys(sanitizedMetadata).length > 0 && { metadata: sanitizedMetadata }),\n        ...(basePayload.inputs && Object.keys(basePayload.inputs).length > 0 && { inputs: basePayload.inputs }),\n        ...(basePayload.context && { context: basePayload.context }),\n      };\n\n      // Diff-only / send-once WebMCP tool dispatch. `buildPayload()` already\n      // snapshotted the full set; `sendWithClientToolsDiff` decides whether to\n      // ship it again or just its fingerprint (retrying once on a 409 registry\n      // miss). The cache is committed only after a successful stream start\n      // (below), so a 409/failure leaves it untouched.\n      const { response, commit: commitClientToolsFingerprint } =\n        await this.sendWithClientToolsDiff(session.sessionId, basePayload.clientTools, (toolFields) => {\n          const chatRequest: ClientChatRequest = { ...baseChatRequest, ...toolFields };\n\n          if (this.debug) {\n            // eslint-disable-next-line no-console\n            console.debug(\"[AgentWidgetClient] client token dispatch\", chatRequest);\n          }\n\n          return fetch(this.getClientApiUrl('chat'), {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n              'X-Persona-Version': VERSION,\n            },\n            body: JSON.stringify(chatRequest),\n            signal: options.signal,\n          });\n        });\n\n      if (!response.ok) {\n        const data = await response.json().catch(() => ({ error: 'Chat request failed' }));\n\n        if (response.status === 401) {\n          // Session expired\n          this.clearClientSession();\n          this.config.onSessionExpired?.();\n          const error = new Error('Session expired. Please refresh to continue.');\n          onEvent({ type: \"error\", error });\n          throw error;\n        }\n\n        if (response.status === 429) {\n          const error = new Error(data.hint || 'Message limit reached for this session.');\n          onEvent({ type: \"error\", error });\n          throw error;\n        }\n\n        const error = new Error(data.error || 'Failed to send message');\n        onEvent({ type: \"error\", error });\n        throw error;\n      }\n\n      if (!response.body) {\n        const error = new Error('No response body received');\n        onEvent({ type: \"error\", error });\n        throw error;\n      }\n\n      // Stream is good: the server now holds this tool set under this\n      // fingerprint for the session. Commit the cache so unchanged follow-up\n      // turns can send fingerprint-only.\n      commitClientToolsFingerprint();\n\n      onEvent({ type: \"status\", status: \"connected\" });\n      \n      // Stream the response (same SSE handling as proxy mode)\n      try {\n        await this.streamResponse(response.body, onEvent, options.assistantMessageId);\n      } finally {\n        onEvent({ type: \"status\", status: \"idle\" });\n      }\n    } catch (error) {\n      const err = error instanceof Error ? error : new Error(String(error));\n      // Only emit error if it wasn't already emitted\n      if (!err.message.includes('Session expired') && !err.message.includes('Message limit')) {\n        onEvent({ type: \"error\", error: err });\n      }\n      throw err;\n    }\n  }\n\n  /**\n   * Proxy mode dispatch (original implementation)\n   */\n  private async dispatchProxy(options: DispatchOptions, onEvent: SSEHandler) {\n    onEvent({ type: \"status\", status: \"connecting\" });\n\n    const payload = await this.buildPayload(options.messages);\n\n    if (this.debug) {\n      // eslint-disable-next-line no-console\n      console.debug(\"[AgentWidgetClient] dispatch payload\", payload);\n    }\n\n    // Build headers - merge static headers with dynamic headers if provided\n    let headers = { ...this.headers };\n    if (this.getHeaders) {\n      try {\n        const dynamicHeaders = await this.getHeaders();\n        headers = { ...headers, ...dynamicHeaders };\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] getHeaders error:\", error);\n        }\n      }\n    }\n\n    // Use customFetch if provided, otherwise use default fetch\n    let response: Response;\n    if (this.customFetch) {\n      try {\n        response = await this.customFetch(\n          this.apiUrl,\n          {\n            method: \"POST\",\n            headers,\n            body: JSON.stringify(payload),\n            signal: options.signal\n          },\n          payload\n        );\n      } catch (error) {\n        const err = error instanceof Error ? error : new Error(String(error));\n        onEvent({ type: \"error\", error: err });\n        throw err;\n      }\n    } else {\n      response = await fetch(this.apiUrl, {\n        method: \"POST\",\n        headers,\n        body: JSON.stringify(payload),\n        signal: options.signal\n      });\n    }\n\n    if (!response.ok || !response.body) {\n      const error = new Error(\n        `Chat backend request failed: ${response.status} ${response.statusText}`\n      );\n      onEvent({ type: \"error\", error });\n      throw error;\n    }\n\n    onEvent({ type: \"status\", status: \"connected\" });\n    try {\n      await this.streamResponse(response.body, onEvent);\n    } finally {\n      onEvent({ type: \"status\", status: \"idle\" });\n    }\n  }\n\n  /**\n   * Agent mode dispatch\n   */\n  private async dispatchAgent(options: DispatchOptions, onEvent: SSEHandler) {\n    onEvent({ type: \"status\", status: \"connecting\" });\n\n    const payload = await this.buildAgentPayload(options.messages);\n\n    if (this.debug) {\n      // eslint-disable-next-line no-console\n      console.debug(\"[AgentWidgetClient] agent dispatch payload\", payload);\n    }\n\n    // Build headers - merge static headers with dynamic headers if provided\n    let headers = { ...this.headers };\n    if (this.getHeaders) {\n      try {\n        const dynamicHeaders = await this.getHeaders();\n        headers = { ...headers, ...dynamicHeaders };\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] getHeaders error:\", error);\n        }\n      }\n    }\n\n    // Use customFetch if provided, otherwise use default fetch\n    let response: Response;\n    if (this.customFetch) {\n      try {\n        response = await this.customFetch(\n          this.apiUrl,\n          {\n            method: \"POST\",\n            headers,\n            body: JSON.stringify(payload),\n            signal: options.signal\n          },\n          payload as unknown as AgentWidgetRequestPayload\n        );\n      } catch (error) {\n        const err = error instanceof Error ? error : new Error(String(error));\n        onEvent({ type: \"error\", error: err });\n        throw err;\n      }\n    } else {\n      response = await fetch(this.apiUrl, {\n        method: \"POST\",\n        headers,\n        body: JSON.stringify(payload),\n        signal: options.signal\n      });\n    }\n\n    if (!response.ok || !response.body) {\n      const error = new Error(\n        `Agent execution request failed: ${response.status} ${response.statusText}`\n      );\n      onEvent({ type: \"error\", error });\n      throw error;\n    }\n\n    onEvent({ type: \"status\", status: \"connected\" });\n    try {\n      await this.streamResponse(response.body, onEvent, options.assistantMessageId);\n    } finally {\n      onEvent({ type: \"status\", status: \"idle\" });\n    }\n  }\n\n  /**\n   * Process an external SSE stream through the SDK's event pipeline.\n   * This allows piping responses from endpoints like agent approval\n   * through the same message/tool/reasoning handling as dispatch().\n   */\n  public async processStream(\n    body: ReadableStream<Uint8Array>,\n    onEvent: SSEHandler,\n    assistantMessageId?: string,\n    seedContent?: string\n  ): Promise<void> {\n    onEvent({ type: \"status\", status: \"connected\" });\n    try {\n      await this.streamResponse(body, onEvent, assistantMessageId, seedContent);\n    } finally {\n      onEvent({ type: \"status\", status: \"idle\" });\n    }\n  }\n\n  /**\n   * Send an approval decision to the API and return the response\n   * for streaming continuation.\n   */\n  public async resolveApproval(\n    approval: { agentId: string; executionId: string; approvalId: string },\n    decision: 'approved' | 'denied'\n  ): Promise<Response> {\n    const baseUrl = this.config.apiUrl\n      ?.replace(/\\/+$/, '')\n      .replace(/\\/v1\\/dispatch$/, '') || DEFAULT_CLIENT_API_BASE;\n    const url = `${baseUrl}/v1/agents/${approval.agentId}/approve`;\n\n    let headers: Record<string, string> = {\n      'Content-Type': 'application/json',\n      ...this.headers\n    };\n    if (this.getHeaders) {\n      Object.assign(headers, await this.getHeaders());\n    }\n\n    return fetch(url, {\n      method: 'POST',\n      headers,\n      body: JSON.stringify({\n        executionId: approval.executionId,\n        approvalId: approval.approvalId,\n        decision,\n        streamResponse: true,\n      }),\n    });\n  }\n\n  /**\n   * Resume a paused flow execution by supplying outputs for LOCAL\n   * (client-executed) tools. Used by the built-in `ask_user_question`\n   * answer-pill sheet, but generic enough for any LOCAL tool.\n   *\n   * Routes by mode:\n   *  - **client-token mode**: POST `${apiBase}/v1/client/resume` (the\n   *    session-authenticated sibling of `/v1/client/chat`; runtypelabs/core#3889),\n   *    with the active `sessionId` in the body and no Bearer key: a browser\n   *    client-token page holds no secret. The page's tool registry is\n   *    re-snapshotted and sent alongside `toolOutputs` via the same diff-only\n   *    `clientTools` / `clientToolsFingerprint` protocol as `/v1/client/chat`\n   *    (runtypelabs/core#5361), so tools registered by a mid-run page\n   *    navigation replace the run's dispatch-time set and become callable on\n   *    the next model turn. Old servers strip the unknown fields and keep the\n   *    frozen-at-dispatch behavior.\n   *  - **dispatch / proxy mode**: POST `${apiUrl}/resume`: Runtype mounts\n   *    resume as a child of `/v1/dispatch`, so the URL is `${apiUrl}/resume`,\n   *    and proxies follow the same shape (`/api/chat/dispatch/resume`).\n   *\n   * Returns the raw Response so the caller can pipe its SSE body through\n   * `connectStream()`.\n   *\n   * @param executionId - The paused execution id carried on `step_await`.\n   * @param toolOutputs - Map keyed by per-call `toolCallId` (core#3878),\n   *   falling back to tool name for legacy servers → the tool's result value.\n   */\n  /**\n   * Diff-only / send-once WebMCP clientTools transport, shared by the\n   * client-token chat (`/v1/client/chat`) and resume (`/v1/client/resume`)\n   * paths — both routes speak the same protocol (runtypelabs/core#5361).\n   *\n   * Decides, against the shared fingerprint cache, whether this request ships\n   * the full `clientTools[]` + fingerprint (first send under this session, or\n   * a changed set) or the fingerprint alone (unchanged set; the server reuses\n   * its stored copy). Runs `doFetch` with the chosen fields and retries\n   * EXACTLY once with the full array on a\n   * `409 { error: 'client_tools_resend_required' }` registry miss — the retry\n   * is 409-*triggered*, never 409-*expected*, so servers predating the\n   * protocol (which strip the unknown fields and never 409) work unchanged.\n   * The 409 body is probed on a `clone()` so the original response body stays\n   * readable by the caller's error handling.\n   *\n   * The cache is NOT committed here: callers invoke the returned `commit()`\n   * only after the server has accepted the request (response OK / stream\n   * started), so a failed request can never record a fingerprint the server\n   * never stored. A resend-required miss invalidates the cached fingerprint\n   * immediately so later turns keep resending in full until a clean success\n   * commits a fresh one.\n   *\n   * `emptyMeansReplace` (resume only): when the live snapshot is empty but a\n   * non-empty set was committed under this session and never explicitly\n   * cleared (the paused tool navigated to a page with no tool registry), ship\n   * an explicit `clientTools: []` so the server REPLACES the persisted\n   * dispatch-time set with nothing and clears its stored registry. Chat keeps\n   * its omit-when-empty behavior: on `/chat`, absent fields already mean \"no\n   * tools this turn\", whereas on `/resume` absence means \"keep the frozen\n   * dispatch-time set\".\n   */\n  private async sendWithClientToolsDiff(\n    sessionId: string,\n    fullClientTools: ClientToolDefinition[] | undefined,\n    doFetch: (\n      toolFields: Pick<ClientChatRequest, 'clientTools' | 'clientToolsFingerprint'>\n    ) => Promise<Response>,\n    opts?: { emptyMeansReplace?: boolean }\n  ): Promise<{ response: Response; commit: () => void }> {\n    const hasClientTools = !!(fullClientTools && fullClientTools.length > 0);\n    const clientToolsFingerprint = hasClientTools\n      ? computeClientToolsFingerprint(fullClientTools!)\n      : undefined;\n    const sameSession = this.clientToolsFingerprintSessionId === sessionId;\n    const unchanged =\n      hasClientTools && sameSession && this.lastSentClientToolsFingerprint === clientToolsFingerprint;\n    // Keyed on `sentNonEmptyClientToolsSessionId`, NOT the fingerprint: an\n    // interleaved empty-tool chat turn commits a null fingerprint without\n    // clearing the tools persisted for a still-paused execution, so the\n    // fingerprint alone would lose the pending clear. The dedicated flag\n    // survives omitted-empty commits and is reset only once an explicit []\n    // replace is confirmed.\n    const sendEmptyReplace =\n      !hasClientTools &&\n      opts?.emptyMeansReplace === true &&\n      this.sentNonEmptyClientToolsSessionId === sessionId;\n\n    // `forceFull` flips to true after a 409 cache-miss so the single retry\n    // resends the full list.\n    let forceFull = false;\n    let response: Response;\n    for (let attempt = 0; ; attempt++) {\n      const sendFull = hasClientTools && (forceFull || !unchanged);\n      response = await doFetch({\n        ...(sendFull && fullClientTools ? { clientTools: fullClientTools } : {}),\n        ...(sendEmptyReplace ? { clientTools: [] } : {}),\n        ...(clientToolsFingerprint ? { clientToolsFingerprint } : {}),\n      });\n\n      // Diff-only cache miss: the server has no stored tool set matching our\n      // fingerprint. Retry exactly once with the full list. A second miss\n      // falls through to the caller's normal error handling (no infinite loop).\n      if (response.status === 409 && attempt === 0 && hasClientTools) {\n        const body = (await response\n          .clone()\n          .json()\n          .catch(() => null)) as { error?: string } | null;\n        if (body?.error === 'client_tools_resend_required') {\n          forceFull = true;\n          // Invalidate so future turns also resend until a clean success\n          // commits a fresh fingerprint.\n          this.lastSentClientToolsFingerprint = null;\n          continue;\n        }\n      }\n      break;\n    }\n\n    return {\n      response,\n      commit: () => {\n        this.lastSentClientToolsFingerprint = clientToolsFingerprint ?? null;\n        this.clientToolsFingerprintSessionId = sessionId;\n        if (hasClientTools) {\n          this.sentNonEmptyClientToolsSessionId = sessionId;\n        } else if (sendEmptyReplace) {\n          // The explicit [] replaced the persisted set server-side; the\n          // pending clear is done.\n          this.sentNonEmptyClientToolsSessionId = null;\n        }\n        // Omitted-empty commits (chat with zero tools) leave the flag set:\n        // they don't touch tools persisted for a paused execution.\n      },\n    };\n  }\n\n  public async resumeFlow(\n    executionId: string,\n    toolOutputs: Record<string, unknown>,\n    options?: { streamResponse?: boolean; signal?: AbortSignal }\n  ): Promise<Response> {\n    const isClientToken = this.isClientTokenMode();\n    const url = isClientToken\n      ? this.getClientApiUrl('resume')\n      : `${this.config.apiUrl?.replace(/\\/+$/, '') || DEFAULT_CLIENT_API_BASE}/resume`;\n\n    // The client-token resume route authenticates the session, not a Bearer\n    // key. A WebMCP approval can sit awaiting user input for a long time, so by\n    // the time we resume the original session may have expired. Re-validate (and\n    // silently re-init if needed) via initSession(): which returns the live\n    // session when `new Date() < expiresAt`, else mints a fresh one: instead of\n    // trusting the possibly-stale `this.clientSession`. (core#3889; BugBot\n    // PR #214 r3367875360.)\n    let resumeSessionId: string | undefined;\n    if (isClientToken) {\n      resumeSessionId = (await this.initSession()).sessionId;\n    }\n\n    let headers: Record<string, string> = {\n      'Content-Type': 'application/json',\n      ...this.headers\n    };\n    if (this.getHeaders) {\n      Object.assign(headers, await this.getHeaders());\n    }\n\n    const body: Record<string, unknown> = {\n      executionId,\n      toolOutputs,\n      streamResponse: options?.streamResponse ?? true,\n    };\n    // Thread the (refreshed) sessionId through like `/v1/client/chat` does.\n    if (resumeSessionId) {\n      body.sessionId = resumeSessionId;\n    }\n\n    if (isClientToken && resumeSessionId) {\n      // Mid-run WebMCP tool refresh (runtypelabs/core#5361): the paused tool\n      // may have navigated the page, so the dispatch-time snapshot the server\n      // persisted can be stale. Re-snapshot the registry — the same built-in +\n      // bridge composition as the payload builders, so fingerprints computed\n      // here and on chat turns describe the same tool space — and ship it via\n      // the shared diff-only protocol. `emptyMeansReplace` sends an explicit\n      // `clientTools: []` when the registry vanished after a non-empty send,\n      // so the server replaces the persisted set instead of keeping it frozen.\n      const fullClientTools = [\n        ...builtInClientToolsForDispatch(this.config),\n        ...((await this.webMcpBridge?.snapshotForDispatch()) ?? []),\n      ];\n      const { response, commit } = await this.sendWithClientToolsDiff(\n        resumeSessionId,\n        fullClientTools,\n        (toolFields) => {\n          const resumeRequest = { ...body, ...toolFields };\n          if (this.debug) {\n            // eslint-disable-next-line no-console\n            console.debug(\"[AgentWidgetClient] client token resume\", resumeRequest);\n          }\n          return fetch(url, {\n            method: 'POST',\n            headers,\n            body: JSON.stringify(resumeRequest),\n            signal: options?.signal,\n          });\n        },\n        { emptyMeansReplace: true }\n      );\n      // The server stores the refreshed registry before running the\n      // continuation pipeline, so an OK response means it holds this set under\n      // this fingerprint. Mirror chat's commit-on-success discipline: a failed\n      // resume must not record a fingerprint the server never stored.\n      if (response.ok) {\n        commit();\n      }\n      return response;\n    }\n\n    return fetch(url, {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(body),\n      signal: options?.signal,\n    });\n  }\n\n  private async buildAgentPayload(\n    messages: AgentWidgetMessage[]\n  ): Promise<AgentWidgetAgentRequestPayload> {\n    const routedAgentId = this.routing().agentId;\n    if (!this.config.agent && !routedAgentId) {\n      throw new Error('Agent configuration required for agent mode');\n    }\n\n    // Filter out messages with empty content and normalize\n    const normalizedMessages = messages\n      .slice()\n      .filter(hasValidContent)\n      .filter(m => m.role === \"user\" || m.role === \"assistant\" || m.role === \"system\")\n      .filter(m => !m.variant || m.variant === \"assistant\")\n      .sort((a, b) => {\n        const timeA = new Date(a.createdAt).getTime();\n        const timeB = new Date(b.createdAt).getTime();\n        return timeA - timeB;\n      })\n      .map((message) => ({\n        role: message.role,\n        content: message.contentParts ?? message.llmContent ?? message.rawContent ?? message.content,\n        createdAt: message.createdAt\n      }));\n\n    const payload: AgentWidgetAgentRequestPayload = {\n      agent: this.config.agent ?? { agentId: routedAgentId! },\n      messages: normalizedMessages,\n      options: {\n        streamResponse: true,\n        recordMode: 'virtual',\n        ...this.config.agentOptions\n      }\n    };\n\n    // Client tools: built-in widget tools (ask_user_question, when exposed)\n    // plus the per-turn WebMCP page-registry snapshot. Name collisions are\n    // impossible: WebMCP entries are `webmcp:`-prefixed server-side while\n    // `sdk`-origin built-ins keep bare names. Both kinds ride the same\n    // diff-only fingerprint path in client-token mode. Kept to a single await\n    // so dispatch microtask timing is unchanged.\n    const clientTools = [\n      ...builtInClientToolsForDispatch(this.config),\n      ...((await this.webMcpBridge?.snapshotForDispatch()) ?? []),\n    ];\n    if (clientTools.length > 0) {\n      payload.clientTools = clientTools;\n    }\n\n    // Add context from providers\n    if (this.contextProviders.length) {\n      const contextAggregate: Record<string, unknown> = {};\n      await Promise.all(\n        this.contextProviders.map(async (provider) => {\n          try {\n            const result = await provider({\n              messages,\n              config: this.config\n            });\n            if (result && typeof result === \"object\") {\n              Object.assign(contextAggregate, result);\n            }\n          } catch (error) {\n            if (typeof console !== \"undefined\") {\n              // eslint-disable-next-line no-console\n              console.warn(\"[AgentWidget] Context provider failed:\", error);\n            }\n          }\n        })\n      );\n\n      if (Object.keys(contextAggregate).length) {\n        payload.context = contextAggregate;\n      }\n    }\n\n    return payload;\n  }\n\n  private async buildPayload(\n    messages: AgentWidgetMessage[]\n  ): Promise<AgentWidgetRequestPayload> {\n    // Filter out messages with empty content to prevent validation errors\n    const normalizedMessages = messages\n      .slice()\n      .filter(hasValidContent)\n      .sort((a, b) => {\n        const timeA = new Date(a.createdAt).getTime();\n        const timeB = new Date(b.createdAt).getTime();\n        return timeA - timeB;\n      })\n      .map((message) => ({\n        role: message.role,\n        // Priority: contentParts (multi-modal) > llmContent (explicit LLM content) > rawContent (structured parsers) > content (display)\n        content: message.contentParts ?? message.llmContent ?? message.rawContent ?? message.content,\n        createdAt: message.createdAt\n      }));\n\n    const routed = this.routing();\n    const payload: AgentWidgetRequestPayload = {\n      messages: normalizedMessages,\n      ...(routed.agentId\n        ? { agent: { agentId: routed.agentId } }\n        : routed.flowId\n          ? { flowId: routed.flowId }\n          : {})\n    };\n\n    // Custom-provider targets (e.g. `eve:support`) resolve to a payload\n    // fragment that is merged into the dispatch body so a BYO backend can read\n    // whatever routing keys its resolver chose. `messages` is authoritative and\n    // can never be overridden by a resolver.\n    if (routed.targetPayload) {\n      for (const [key, value] of Object.entries(routed.targetPayload)) {\n        if (key === \"messages\") continue;\n        (payload as Record<string, unknown>)[key] = value;\n      }\n    }\n\n    // Client tools: same built-in + WebMCP merge as buildAgentPayload\n    // (flow-dispatch path).\n    const clientTools = [\n      ...builtInClientToolsForDispatch(this.config),\n      ...((await this.webMcpBridge?.snapshotForDispatch()) ?? []),\n    ];\n    if (clientTools.length > 0) {\n      payload.clientTools = clientTools;\n    }\n\n    if (this.contextProviders.length) {\n      const contextAggregate: Record<string, unknown> = {};\n      await Promise.all(\n        this.contextProviders.map(async (provider) => {\n          try {\n            const result = await provider({\n              messages,\n              config: this.config\n            });\n            if (result && typeof result === \"object\") {\n              Object.assign(contextAggregate, result);\n            }\n          } catch (error) {\n            if (typeof console !== \"undefined\") {\n              // eslint-disable-next-line no-console\n              console.warn(\"[AgentWidget] Context provider failed:\", error);\n            }\n          }\n        })\n      );\n\n      if (Object.keys(contextAggregate).length) {\n        payload.context = contextAggregate;\n      }\n    }\n\n    if (this.requestMiddleware) {\n      try {\n        const result = await this.requestMiddleware({\n          payload: { ...payload },\n          config: this.config\n        });\n        if (result && typeof result === \"object\") {\n          const next = result as AgentWidgetRequestPayload;\n          // Preserve `clientTools` if the middleware returned a fresh\n          // payload object without it. Naive middlewares often rebuild\n          // the payload by listing the fields they care about and\n          // dropping `clientTools` accidentally; the WebMCP wire surface\n          // is invisible to them. The integrator can still set\n          // `clientTools: []` or `clientTools: undefined` explicitly to\n          // strip them on purpose: we only fall back when the field is\n          // entirely absent from the returned object.\n          if (\n            payload.clientTools !== undefined &&\n            !(\"clientTools\" in next)\n          ) {\n            next.clientTools = payload.clientTools;\n          }\n          return next;\n        }\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Request middleware error:\", error);\n        }\n      }\n    }\n\n    return payload;\n  }\n\n  /**\n   * Handle custom SSE event parsing via parseSSEEvent callback\n   * Returns true if event was handled, false otherwise\n   */\n  private async handleCustomSSEEvent(\n    payload: unknown,\n    onEvent: SSEHandler,\n    assistantMessageRef: { current: AgentWidgetMessage | null },\n    emitMessage: (msg: AgentWidgetMessage) => void,\n    nextSequence: () => number,\n    partIdState: { current: string | null }\n  ): Promise<boolean> {\n    if (!this.parseSSEEvent) return false;\n\n    try {\n      const result = await this.parseSSEEvent(payload);\n      if (result === null) return false; // Event should be ignored\n\n      const createNewAssistant = (partId?: string): AgentWidgetMessage => {\n        const msg: AgentWidgetMessage = {\n          id: `assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`,\n          role: \"assistant\",\n          content: \"\",\n          createdAt: new Date().toISOString(),\n          streaming: true,\n          variant: \"assistant\",\n          sequence: nextSequence(),\n          ...(partId !== undefined && { partId })\n        };\n        assistantMessageRef.current = msg;\n        emitMessage(msg);\n        return msg;\n      };\n\n      const ensureAssistant = (partId?: string) => {\n        if (assistantMessageRef.current) return assistantMessageRef.current;\n        return createNewAssistant(partId);\n      };\n\n      if (result.text !== undefined) {\n        // partId-based message segmentation: when partId changes, seal current\n        // message and start a new one for chronological tool/text interleaving\n        if (result.partId !== undefined && partIdState.current !== null && result.partId !== partIdState.current) {\n          // Seal the current assistant message\n          if (assistantMessageRef.current) {\n            assistantMessageRef.current.streaming = false;\n            emitMessage(assistantMessageRef.current);\n          }\n          // Create a new assistant message for the new text segment\n          createNewAssistant(result.partId);\n        }\n\n        // Update partId tracking (only when partId is provided: backward compatible)\n        if (result.partId !== undefined) {\n          partIdState.current = result.partId;\n        }\n\n        const assistant = ensureAssistant(result.partId);\n        // Tag the message with partId if present and not already set\n        if (result.partId !== undefined && !assistant.partId) {\n          assistant.partId = result.partId;\n        }\n        assistant.content += result.text;\n        emitMessage(assistant);\n      }\n\n      if (result.done) {\n        if (assistantMessageRef.current) {\n          assistantMessageRef.current.streaming = false;\n          emitMessage(assistantMessageRef.current);\n        }\n        partIdState.current = null;\n        onEvent({ type: \"status\", status: \"idle\" });\n      }\n\n      if (result.error) {\n        partIdState.current = null;\n        onEvent({\n          type: \"error\",\n          error: new Error(result.error)\n        });\n      }\n\n      return true; // Event was handled\n    } catch (error) {\n      if (typeof console !== \"undefined\") {\n        // eslint-disable-next-line no-console\n        console.error(\"[AgentWidget] parseSSEEvent error:\", error);\n      }\n      return false;\n    }\n  }\n\n  private async streamResponse(\n    body: ReadableStream<Uint8Array>,\n    onEvent: SSEHandler,\n    assistantMessageId?: string,\n    // Durable reconnect: seed the assistant accumulator with the text already\n    // shown before the drop, so replayed post-cursor deltas APPEND to it\n    // instead of a fresh stream clobbering it (the replay carries only\n    // `seq > after`, i.e. the new deltas, not the full text).\n    seedContent?: string\n  ) {\n    const reader = body.getReader();\n    const decoder = new TextDecoder();\n    let buffer = \"\";\n\n    const baseSequence = Date.now();\n    let sequenceCounter = 0;\n    const nextSequence = () => baseSequence + sequenceCounter++;\n\n    const cloneMessage = (msg: AgentWidgetMessage): AgentWidgetMessage => {\n      const reasoning = msg.reasoning\n        ? {\n            ...msg.reasoning,\n            chunks: [...msg.reasoning.chunks]\n          }\n        : undefined;\n      const toolCall = msg.toolCall\n        ? {\n            ...msg.toolCall,\n            chunks: msg.toolCall.chunks ? [...msg.toolCall.chunks] : undefined\n          }\n        : undefined;\n      const tools = msg.tools\n        ? msg.tools.map((tool) => ({\n            ...tool,\n            chunks: tool.chunks ? [...tool.chunks] : undefined\n          }))\n        : undefined;\n\n      return {\n        ...msg,\n        reasoning,\n        toolCall,\n        tools\n      };\n    };\n\n    const shouldEmitMessage = (msg: AgentWidgetMessage): boolean => {\n      if (msg.role !== \"assistant\" || msg.variant) return true;\n\n      const hasContentParts =\n        Array.isArray(msg.contentParts) && msg.contentParts.length > 0;\n      const hasRawContent =\n        typeof msg.rawContent === \"string\" && msg.rawContent.trim() !== \"\";\n      const hasVisibleText =\n        typeof msg.content === \"string\" && msg.content.trim() !== \"\";\n\n      // Do not surface assistant text bubbles that only contain whitespace.\n      // Some providers emit newline-only text parts around a leading tool call;\n      // rendering those as normal messages creates an empty bubble above the\n      // tool card. Keep media/component/stop-reason messages renderable.\n      return hasVisibleText || hasContentParts || hasRawContent || Boolean(msg.stopReason);\n    };\n\n    const emitMessage = (msg: AgentWidgetMessage) => {\n      if (!shouldEmitMessage(msg)) return;\n      onEvent({\n        type: \"message\",\n        message: cloneMessage(msg)\n      });\n    };\n\n    let assistantMessage: AgentWidgetMessage | null = null;\n    // Tracks the most recently touched assistant text message for the\n    // current agent turn so `agent_turn_complete.stopReason` can attach\n    // to the final visible text segment even after `assistantMessage`\n    // has been finalized at a tool-call boundary within the turn.\n    let lastAssistantInTurn: AgentWidgetMessage | null = null;\n    // Reference to track assistant message for custom event handler\n    const assistantMessageRef = { current: null as AgentWidgetMessage | null };\n    // Segmentation state for the `parseSSEEvent` extensibility callback (the\n    // consumer's own `partId` field) — independent of the wire.\n    const customParsePartId = { current: null as string | null };\n    // Unified text-channel block id (from `text_start`/`text_delta` `id`). Drives\n    // bubble-id segmentation on the wire in place of the legacy `partId`:\n    // a new block id means a new bubble, sealed at `text_complete`/tool boundaries.\n    let currentTextBlockId: string | null = null;\n    // Raw text accumulated for the open flow block before its bubble is\n    // materialized — lets a whitespace-only block resolve without a stray bubble.\n    let pendingFlowRaw = \"\";\n    // Nested flow-as-tool attribution (PR #4602): a text/reasoning block whose\n    // `parentToolCallId` matches a `tool_start.toolCallId` belongs to a flow\n    // running as that tool. Keyed by the wire block id, these route the block's\n    // deltas into a message tagged `agentMetadata.parentToolId` (the parent tool's\n    // row) instead of the top-level assistant/reasoning channel.\n    const nestedBlockParent = new Map<string, string>();\n    const nestedBlockMessages = new Map<string, AgentWidgetMessage>();\n    const nestedBlockRaw = new Map<string, string>();\n    const reasoningMessages = new Map<string, AgentWidgetMessage>();\n    const toolMessages = new Map<string, AgentWidgetMessage>();\n    const reasoningContext = {\n      lastId: null as string | null,\n      byStep: new Map<string, string>()\n    };\n    const toolContext = {\n      lastId: null as string | null,\n      byCall: new Map<string, string>()\n    };\n\n    const normalizeKey = (value: unknown): string | null => {\n      if (value === null || value === undefined) return null;\n      try {\n        return String(value);\n      } catch (error) {\n        return null;\n      }\n    };\n\n    const getStepKey = (payload: Record<string, any>) =>\n      normalizeKey(\n        payload.stepId ??\n          payload.step_id ??\n          payload.step ??\n          payload.parentId ??\n          payload.flowStepId ??\n          payload.flow_step_id\n      );\n\n    const getToolCallKey = (payload: Record<string, any>) =>\n      normalizeKey(\n        payload.callId ??\n          payload.call_id ??\n          payload.requestId ??\n          payload.request_id ??\n          payload.toolCallId ??\n          payload.tool_call_id ??\n          payload.stepId ??\n          payload.step_id\n      );\n\n    const baseAssistantId = assistantMessageId;\n    let assistantIdConsumed = false;\n\n    const ensureAssistantMessage = () => {\n      if (assistantMessage) return assistantMessage;\n      let id: string;\n      let initialContent = \"\";\n      const segment = currentTextBlockId;\n      if (!assistantIdConsumed && baseAssistantId) {\n        id = baseAssistantId;\n        assistantIdConsumed = true;\n        // First (and only) time we reuse the caller-supplied id: this is the\n        // bubble a durable reconnect resumes into, so continue its text.\n        initialContent = seedContent ?? \"\";\n      } else if (baseAssistantId && segment) {\n        id = `${baseAssistantId}_${segment}`;\n      } else {\n        id = `assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n      }\n      assistantMessage = {\n        id,\n        role: \"assistant\",\n        content: initialContent,\n        createdAt: new Date().toISOString(),\n        streaming: true,\n        sequence: nextSequence()\n      };\n      emitMessage(assistantMessage);\n      return assistantMessage;\n    };\n\n    const trackReasoningId = (stepKey: string | null, id: string) => {\n      reasoningContext.lastId = id;\n      if (stepKey) {\n        reasoningContext.byStep.set(stepKey, id);\n      }\n    };\n\n    const resolveReasoningId = (\n      payload: Record<string, any>,\n      allowCreate: boolean\n    ): string | null => {\n      const rawId = payload.reasoningId ?? payload.id;\n      const stepKey = getStepKey(payload);\n      if (rawId) {\n        const resolved = String(rawId);\n        trackReasoningId(stepKey, resolved);\n        return resolved;\n      }\n      if (stepKey) {\n        const existing = reasoningContext.byStep.get(stepKey);\n        if (existing) {\n          reasoningContext.lastId = existing;\n          return existing;\n        }\n      }\n      if (reasoningContext.lastId && !allowCreate) {\n        return reasoningContext.lastId;\n      }\n      if (!allowCreate) {\n        return null;\n      }\n      const generated = `reason-${nextSequence()}`;\n      trackReasoningId(stepKey, generated);\n      return generated;\n    };\n\n    const ensureReasoningMessage = (reasoningId: string) => {\n      const existing = reasoningMessages.get(reasoningId);\n      if (existing) {\n        return existing;\n      }\n\n      const message: AgentWidgetMessage = {\n        id: `reason-${reasoningId}`,\n        role: \"assistant\",\n        content: \"\",\n        createdAt: new Date().toISOString(),\n        streaming: true,\n        variant: \"reasoning\",\n        sequence: nextSequence(),\n        reasoning: {\n          id: reasoningId,\n          status: \"streaming\",\n          chunks: []\n        }\n      };\n\n      reasoningMessages.set(reasoningId, message);\n      emitMessage(message);\n      return message;\n    };\n\n    const trackToolId = (callKey: string | null, id: string) => {\n      toolContext.lastId = id;\n      if (callKey) {\n        toolContext.byCall.set(callKey, id);\n      }\n    };\n\n    // Track tool call IDs for artifact emit tools so we can suppress their UI\n    const artifactToolCallIds = new Set<string>();\n    // Track artifact block messages (reference card or inline block) so we can\n    // update them on artifact_complete\n    const artifactCardMessages = new Map<string, AgentWidgetMessage>();\n    // Track artifact IDs that already have a reference card (from auto-creation or transcript_insert)\n    const artifactIdsWithCards = new Set<string>();\n    // Accumulate artifact markdown content (and component props) for embedding\n    // in block props on complete. `props` accumulates across `artifact_update`\n    // events so an inline component block hydrates with its real props after a\n    // refresh (the session artifact registry, which also tracks them live, is\n    // not persisted).\n    const artifactContent = new Map<\n      string,\n      {\n        markdown: string;\n        title?: string;\n        file?: PersonaArtifactFileMeta;\n        props?: Record<string, unknown>;\n      }\n    >();\n    const isArtifactEmitToolName = (name: string | undefined): boolean => {\n      if (!name) return false;\n      const normalized = name.replace(/_+/g, \"_\").replace(/^_|_$/g, \"\");\n      return normalized === \"emit_artifact_markdown\" || normalized === \"emit_artifact_component\";\n    };\n\n    const resolveToolId = (\n      payload: Record<string, any>,\n      allowCreate: boolean\n    ): string | null => {\n      const rawId = payload.toolId ?? payload.id;\n      const callKey = getToolCallKey(payload);\n      if (rawId) {\n        const resolved = String(rawId);\n        trackToolId(callKey, resolved);\n        return resolved;\n      }\n      if (callKey) {\n        const existing = toolContext.byCall.get(callKey);\n        if (existing) {\n          toolContext.lastId = existing;\n          return existing;\n        }\n      }\n      if (toolContext.lastId && !allowCreate) {\n        return toolContext.lastId;\n      }\n      if (!allowCreate) {\n        return null;\n      }\n      const generated = `tool-${nextSequence()}`;\n      trackToolId(callKey, generated);\n      return generated;\n    };\n\n    const ensureToolMessage = (toolId: string) => {\n      const existing = toolMessages.get(toolId);\n      if (existing) {\n        return existing;\n      }\n\n      const message: AgentWidgetMessage = {\n        id: `tool-${toolId}`,\n        role: \"assistant\",\n        content: \"\",\n        createdAt: new Date().toISOString(),\n        streaming: true,\n        variant: \"tool\",\n        sequence: nextSequence(),\n        toolCall: {\n          id: toolId,\n          status: \"pending\"\n        }\n      };\n\n      toolMessages.set(toolId, message);\n      emitMessage(message);\n      return message;\n    };\n\n    const resolveTimestamp = (value: unknown) => {\n      if (typeof value === \"number\" && Number.isFinite(value)) {\n        return value;\n      }\n      if (typeof value === \"string\") {\n        const parsed = Number(value);\n        if (!Number.isNaN(parsed) && Number.isFinite(parsed)) {\n          return parsed;\n        }\n        const dateParsed = Date.parse(value);\n        if (!Number.isNaN(dateParsed)) {\n          return dateParsed;\n        }\n      }\n      return Date.now();\n    };\n\n    const ensureStringContent = (value: unknown): string => {\n      if (typeof value === \"string\") {\n        return value;\n      }\n      if (value === null || value === undefined) {\n        return \"\";\n      }\n      // Convert objects/arrays to JSON string\n      try {\n        return JSON.stringify(value);\n      } catch {\n        return String(value);\n      }\n    };\n\n    // Maintain stateful stream parsers per message for incremental parsing\n    const streamParsers = new Map<string, AgentWidgetStreamParser>();\n    // Track accumulated raw content for structured formats (JSON, XML, etc.)\n    const rawContentBuffers = new Map<string, string>();\n    // Rebuild incremental text by sequence so late arrivals can repair already-emitted\n    // content after the reorder buffer's gap-timeout flush.\n    const orderedChunkBuffers = new Map<string, Array<{ seq: number; text: string }>>();\n\n    const insertOrderedChunk = (key: string, seq: number, text: string): string => {\n      let chunks = orderedChunkBuffers.get(key);\n      if (!chunks) {\n        chunks = [];\n        orderedChunkBuffers.set(key, chunks);\n      }\n\n      let lo = 0;\n      let hi = chunks.length;\n      while (lo < hi) {\n        const mid = (lo + hi) >>> 1;\n        if (chunks[mid].seq < seq) {\n          lo = mid + 1;\n        } else {\n          hi = mid;\n        }\n      }\n\n      if (chunks[lo]?.seq === seq) {\n        chunks[lo] = { seq, text };\n      } else {\n        chunks.splice(lo, 0, { seq, text });\n      }\n\n      let accumulated = \"\";\n      for (let index = 0; index < chunks.length; index++) {\n        accumulated += chunks[index].text;\n      }\n      return accumulated;\n    };\n\n    /**\n     * After text_end + didSplitByPartId, merge the authoritative final response into the\n     * sealed message when streaming left content short (e.g. async parser lag).\n     */\n    const reconcileSealedAssistantWithFinalResponse = (\n      msg: AgentWidgetMessage,\n      finalContent: unknown\n    ) => {\n      const finalString = ensureStringContent(finalContent);\n      const rawBuffer = rawContentBuffers.get(msg.id);\n      const contentToProcess = preferFinalStructuredContent(rawBuffer, finalString);\n      msg.rawContent = contentToProcess;\n      const parser = streamParsers.get(msg.id);\n\n      const mergeIfBetter = (mergedDisplay: string) => {\n        const cur = msg.content ?? \"\";\n        if (mergedDisplay.trim() === \"\") return;\n        // Only replace when empty, or when the stream left a strict prefix of the\n        // authoritative final (truncation). Do not use length alone: multi-segment\n        // flows can have a short last bubble whose content is not a prefix of the\n        // full step response.\n        if (\n          cur.trim().length === 0 ||\n          mergedDisplay.startsWith(cur) ||\n          mergedDisplay.trimStart().startsWith(cur.trim())\n        ) {\n          msg.content = mergedDisplay;\n        }\n      };\n\n      const finalizeCleanup = () => {\n        if (parser) {\n          const closeResult = parser.close?.();\n          if (closeResult instanceof Promise) closeResult.catch(() => {});\n        }\n        streamParsers.delete(msg.id);\n        rawContentBuffers.delete(msg.id);\n        msg.streaming = false;\n        emitMessage(msg);\n      };\n\n      if (!parser) {\n        mergeIfBetter(finalString);\n        finalizeCleanup();\n        return;\n      }\n\n      // Prefer JSON fast path when the final payload is JSON-shaped\n      const extractedFromJson = extractTextFromJson(contentToProcess);\n      if (extractedFromJson !== null && extractedFromJson.trim() !== \"\") {\n        mergeIfBetter(extractedFromJson);\n        finalizeCleanup();\n        return;\n      }\n\n      const bestDisplayText = (\n        result: AgentWidgetStreamParserResult | string | null\n      ): string => {\n        const text =\n          typeof result === \"string\" ? result : result?.text ?? null;\n        if (text !== null && text.trim() !== \"\") return text;\n        const extracted = parser.getExtractedText();\n        if (extracted !== null && extracted.trim() !== \"\") return extracted;\n        return finalString;\n      };\n\n      let parsedResult: ReturnType<typeof parser.processChunk>;\n      try {\n        parsedResult = parser.processChunk(contentToProcess);\n      } catch {\n        mergeIfBetter(finalString);\n        finalizeCleanup();\n        return;\n      }\n\n      if (parsedResult instanceof Promise) {\n        parsedResult\n          .then((result) => {\n            mergeIfBetter(bestDisplayText(result));\n            finalizeCleanup();\n          })\n          .catch(() => {\n            mergeIfBetter(finalString);\n            finalizeCleanup();\n          });\n        return;\n      }\n\n      mergeIfBetter(bestDisplayText(parsedResult));\n      finalizeCleanup();\n    };\n\n    // === Unified flow text channel ===\n    // Flow prompt-step text streams as `text_delta` blocks (segmented by\n    // `text_start`/`text_complete`) and can be structured JSON, so each block\n    // runs through the per-bubble structured-content parser — agent text stays\n    // plain. This is the legacy step_delta parser core, re-keyed from `partId`\n    // to the wire block-id bubble. The caller materializes the bubble lazily\n    // (whitespace-only blocks around tool boundaries never leave a stray bubble)\n    // and `step_complete.result.response` reconciles the authoritative final.\n    let lastSealedFlowBubble: AgentWidgetMessage | null = null;\n\n    // Stream one accumulated chunk of flow block text through the parser, setting\n    // display `content` (extracted) + `rawContent` (raw) and emitting. Mirrors the\n    // legacy step_delta chunk path; plain text bypasses the structured parser.\n    const applyFlowTextChunk = (\n      assistant: AgentWidgetMessage,\n      accumulatedRaw: string,\n      chunk: string,\n      chunkSeq: number | undefined\n    ) => {\n      assistant.rawContent = accumulatedRaw;\n      if (!streamParsers.has(assistant.id)) {\n        streamParsers.set(assistant.id, this.createStreamParser());\n      }\n      const parser = streamParsers.get(assistant.id)!;\n      const looksLikeJson =\n        accumulatedRaw.trim().startsWith(\"{\") || accumulatedRaw.trim().startsWith(\"[\");\n      if (looksLikeJson) {\n        rawContentBuffers.set(assistant.id, accumulatedRaw);\n      }\n      const isPlainTextParser = (parser as any).__isPlainTextParser === true;\n      if (isPlainTextParser) {\n        assistant.content =\n          chunkSeq !== undefined ? accumulatedRaw : assistant.content + chunk;\n        rawContentBuffers.delete(assistant.id);\n        streamParsers.delete(assistant.id);\n        assistant.rawContent = undefined;\n        emitMessage(assistant);\n        return;\n      }\n      const parsedResult = parser.processChunk(accumulatedRaw);\n      if (parsedResult instanceof Promise) {\n        parsedResult\n          .then((result) => {\n            const text = typeof result === \"string\" ? result : result?.text ?? null;\n            if (text !== null && text.trim() !== \"\") {\n              assistant.content = text;\n              emitMessage(assistant);\n            } else if (!looksLikeJson && !accumulatedRaw.trim().startsWith(\"<\")) {\n              assistant.content =\n                chunkSeq !== undefined ? accumulatedRaw : assistant.content + chunk;\n              rawContentBuffers.delete(assistant.id);\n              streamParsers.delete(assistant.id);\n              assistant.rawContent = undefined;\n              emitMessage(assistant);\n            }\n          })\n          .catch(() => {\n            assistant.content =\n              chunkSeq !== undefined ? accumulatedRaw : assistant.content + chunk;\n            rawContentBuffers.delete(assistant.id);\n            streamParsers.delete(assistant.id);\n            assistant.rawContent = undefined;\n            emitMessage(assistant);\n          });\n      } else {\n        const text =\n          typeof parsedResult === \"string\" ? parsedResult : parsedResult?.text ?? null;\n        if (text !== null && text.trim() !== \"\") {\n          assistant.content = text;\n          emitMessage(assistant);\n        } else if (!looksLikeJson && !accumulatedRaw.trim().startsWith(\"<\")) {\n          assistant.content =\n            chunkSeq !== undefined ? accumulatedRaw : assistant.content + chunk;\n          rawContentBuffers.delete(assistant.id);\n          streamParsers.delete(assistant.id);\n          assistant.rawContent = undefined;\n          emitMessage(assistant);\n        }\n      }\n    };\n\n    // Seal a flow text block at `text_complete`: run final structured extraction\n    // off the accumulated raw buffer (U2: `text_complete.text` mirrors that raw\n    // buffer, so we never double-count), then finalize the bubble. The structured\n    // `step_complete.result.response` reconciles afterward.\n    const finalizeFlowTextBlock = (\n      assistant: AgentWidgetMessage,\n      finalContent?: unknown\n    ) => {\n      const effectiveFinal =\n        finalContent !== undefined && finalContent !== null\n          ? finalContent\n          : assistant.content;\n      if (\n        effectiveFinal === undefined ||\n        effectiveFinal === null ||\n        effectiveFinal === \"\"\n      ) {\n        assistant.streaming = false;\n        emitMessage(assistant);\n        return;\n      }\n      const rawBuffer = rawContentBuffers.get(assistant.id);\n      const contentToProcess = rawBuffer ?? ensureStringContent(effectiveFinal);\n      assistant.rawContent = contentToProcess;\n      const parser = streamParsers.get(assistant.id);\n      let extractedText: string | null = null;\n      let asyncPending = false;\n      if (parser) {\n        extractedText = parser.getExtractedText();\n        if (extractedText === null) {\n          extractedText = extractTextFromJson(contentToProcess);\n        }\n        if (extractedText === null) {\n          const parsedResult = parser.processChunk(contentToProcess);\n          if (parsedResult instanceof Promise) {\n            asyncPending = true;\n            parsedResult\n              .then((result) => {\n                const text =\n                  typeof result === \"string\" ? result : result?.text ?? null;\n                if (text !== null) {\n                  assistant.content = text;\n                  assistant.streaming = false;\n                  streamParsers.delete(assistant.id);\n                  rawContentBuffers.delete(assistant.id);\n                  emitMessage(assistant);\n                }\n              })\n              .catch(() => {});\n          } else {\n            extractedText =\n              typeof parsedResult === \"string\"\n                ? parsedResult\n                : parsedResult?.text ?? null;\n          }\n        }\n      }\n      if (!asyncPending) {\n        if (extractedText !== null && extractedText.trim() !== \"\") {\n          assistant.content = extractedText;\n        } else if (!rawContentBuffers.has(assistant.id)) {\n          assistant.content = ensureStringContent(effectiveFinal);\n        }\n        const parserToClose = streamParsers.get(assistant.id);\n        if (parserToClose) {\n          const closeResult = parserToClose.close?.();\n          if (closeResult instanceof Promise) closeResult.catch(() => {});\n          streamParsers.delete(assistant.id);\n        }\n        rawContentBuffers.delete(assistant.id);\n        assistant.streaming = false;\n        emitMessage(assistant);\n      }\n    };\n\n    // Materialize (lazily) the message for a nested flow-as-tool block, tagged\n    // with the parent tool-call id so the UI renders it in the parent tool's row.\n    const ensureNestedBlockMessage = (\n      blockId: string,\n      parentToolCallId: string,\n      variant?: \"reasoning\"\n    ): AgentWidgetMessage => {\n      const existing = nestedBlockMessages.get(blockId);\n      if (existing) return existing;\n      const message: AgentWidgetMessage = {\n        id: `nested-${parentToolCallId}-${blockId}`,\n        role: \"assistant\",\n        content: \"\",\n        createdAt: new Date().toISOString(),\n        streaming: true,\n        sequence: nextSequence(),\n        ...(variant ? { variant } : {}),\n        ...(variant === \"reasoning\"\n          ? { reasoning: { id: blockId, status: \"streaming\", chunks: [] } }\n          : {}),\n        agentMetadata: { parentToolId: parentToolCallId },\n      };\n      nestedBlockMessages.set(blockId, message);\n      emitMessage(message);\n      return message;\n    };\n\n    // Ready queue of parsed wire frames awaiting a drain. The API streams the\n    // 33-event wire vocabulary; each frame is parsed in the SSE loop\n    // below and rendered directly by the handler (no translation bridge), then\n    // pushed here. The wire stream is a single, in-order SSE connection, so\n    // frames drain straight through with no reordering.\n    const seqReadyQueue: Array<{ payloadType: string; payload: any }> = [];\n    // Declared here so later closures can reference it; assigned after all\n    // handler-scoped variables are initialised (before the SSE loop).\n    let drainReadyQueue: () => void;\n    // Per-stream media-block buffer: the media triad\n    // (media_start/media_delta/media_complete) is reassembled here into a single\n    // synthetic message at media_complete, keyed by the block id.\n    const mediaBuffers = new Map<\n      string,\n      { mediaType?: string; role?: string; toolCallId?: unknown; parts: string[] }\n    >();\n    // Tracks the last iteration surfaced as a per-iteration message boundary, so\n    // `turn_start` advancing the iteration rotates the bubble in 'separate' mode.\n    let lastIterationSeen = 0;\n    // Execution kind, resolved from the leading `execution_start` frame. Drives\n    // the agent-vs-flow branches that the single wire vocabulary collapses.\n    let executionKind: \"agent\" | \"flow\" = \"agent\";\n    // Whether `executionKind` was set authoritatively by an `execution_start`\n    // frame. Continuation streams (e.g. a tool-driven `/resume`) do NOT re-emit\n    // `execution_start`, so a fresh `streamResponse` for the continuation starts\n    // with the default `\"agent\"`. For a flow that mis-routes the final\n    // prompt-step finalization and duplicates the last message (the streamed\n    // text block is sealed, then `step_complete.result.response` re-renders it as\n    // a second bubble). When `execution_start` is absent we recover the flow kind\n    // from the first flow `step_*` frame below.\n    let executionKindResolved = false;\n    // Open turn id (from `turn_start`). Unified text/reasoning deltas carry their\n    // own block id, not the turn id, so the turn id is threaded onto agentMetadata\n    // from here.\n    let openTurnId: string | null = null;\n    // Agent execution state tracking\n    let agentExecution: AgentExecutionState | null = null;\n    // Track assistant messages per agent iteration for 'separate' mode\n    const agentIterationMessages = new Map<number, AgentWidgetMessage>();\n    const iterationDisplay = this.config.iterationDisplay ?? 'separate';\n\n    // Drains the queued transduced events through the main event handler.\n    // Also invoked after the SSE loop exits so any events queued at\n    // end-of-stream are processed.\n    drainReadyQueue = () => {\n      for (let i = 0; i < seqReadyQueue.length; i++) {\n        const payloadType = seqReadyQueue[i].payloadType;\n        const payload = seqReadyQueue[i].payload;\n\n        // Recover the execution kind on continuation streams that omit\n        // `execution_start` (e.g. a tool-driven `/resume`). Flow `step_*` frames\n        // carry a `stepType`; agent loops never do (they use `turn_*`). Without\n        // this, the continuation defaults to `\"agent\"` and a flow's final\n        // prompt-step finalization is duplicated. We only infer when no\n        // `execution_start` resolved the kind, so an explicit `agent` is never\n        // overridden.\n        if (\n          !executionKindResolved &&\n          executionKind !== \"flow\" &&\n          typeof (payload as { stepType?: unknown }).stepType === \"string\"\n        ) {\n          executionKind = \"flow\";\n        }\n\n        if (payloadType === \"reasoning_start\") {\n          // Nested flow-as-tool thinking (PR #4602): route to the parent tool's row.\n          const rStartBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          const rStartParent =\n            typeof payload.parentToolCallId === \"string\" && payload.parentToolCallId\n              ? payload.parentToolCallId\n              : null;\n          if (rStartBlockId && rStartParent) {\n            nestedBlockParent.set(rStartBlockId, rStartParent);\n            ensureNestedBlockMessage(rStartBlockId, rStartParent, \"reasoning\");\n            continue;\n          }\n          const reasoningId =\n            resolveReasoningId(payload, true) ?? `reason-${nextSequence()}`;\n          const reasoningMessage = ensureReasoningMessage(reasoningId);\n          reasoningMessage.reasoning = reasoningMessage.reasoning ?? {\n            id: reasoningId,\n            status: \"streaming\",\n            chunks: []\n          };\n          reasoningMessage.reasoning.startedAt =\n            reasoningMessage.reasoning.startedAt ??\n            resolveTimestamp(payload.startedAt ?? payload.timestamp);\n          reasoningMessage.reasoning.completedAt = undefined;\n          reasoningMessage.reasoning.durationMs = undefined;\n          if (payload.scope === \"loop\" || payload.scope === \"turn\") {\n            reasoningMessage.reasoning.scope = payload.scope;\n          }\n          reasoningMessage.streaming = true;\n          reasoningMessage.reasoning.status = \"streaming\";\n          emitMessage(reasoningMessage);\n        } else if (payloadType === \"reasoning_delta\") {\n          // Nested flow-as-tool thinking: append to the parent-tool-row message.\n          const rDeltaBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          if (\n            rDeltaBlockId &&\n            nestedBlockParent.has(rDeltaBlockId) &&\n            nestedBlockMessages.has(rDeltaBlockId)\n          ) {\n            const nested = nestedBlockMessages.get(rDeltaBlockId)!;\n            const nestedChunk =\n              payload.reasoningText ?? payload.text ?? payload.delta ?? \"\";\n            if (nestedChunk && payload.hidden !== true && nested.reasoning) {\n              nested.reasoning.chunks.push(String(nestedChunk));\n              emitMessage(nested);\n            }\n            continue;\n          }\n          const reasoningId =\n            resolveReasoningId(payload, false) ??\n            resolveReasoningId(payload, true) ??\n            `reason-${nextSequence()}`;\n          const reasoningMessage = ensureReasoningMessage(reasoningId);\n          reasoningMessage.reasoning = reasoningMessage.reasoning ?? {\n            id: reasoningId,\n            status: \"streaming\",\n            chunks: []\n          };\n          reasoningMessage.reasoning.startedAt =\n            reasoningMessage.reasoning.startedAt ??\n            resolveTimestamp(payload.startedAt ?? payload.timestamp);\n          const chunk =\n            payload.reasoningText ??\n            payload.text ??\n            payload.delta ??\n            \"\";\n          if (chunk && payload.hidden !== true) {\n            const reasonSeq = typeof payload.sequenceIndex === \"number\" ? payload.sequenceIndex : undefined;\n            if (reasonSeq !== undefined) {\n              // Rebuild chunks by seq so late arrivals after a gap-timeout flush\n              // are inserted at the correct position rather than appended.\n              const ordered = insertOrderedChunk(reasoningId, reasonSeq, String(chunk));\n              reasoningMessage.reasoning.chunks = [ordered];\n            } else {\n              reasoningMessage.reasoning.chunks.push(String(chunk));\n            }\n          }\n          reasoningMessage.reasoning.status = payload.done ? \"complete\" : \"streaming\";\n          if (payload.done) {\n            reasoningMessage.reasoning.completedAt = resolveTimestamp(\n              payload.completedAt ?? payload.timestamp\n            );\n            const start = reasoningMessage.reasoning.startedAt ?? Date.now();\n            reasoningMessage.reasoning.durationMs = Math.max(\n              0,\n              (reasoningMessage.reasoning.completedAt ?? Date.now()) - start\n            );\n\n          }\n          reasoningMessage.streaming = reasoningMessage.reasoning.status !== \"complete\";\n          emitMessage(reasoningMessage);\n        } else if (payloadType === \"reasoning_complete\") {\n          // Nested flow-as-tool thinking close: seal the parent-tool-row message.\n          const rCompleteBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          if (\n            rCompleteBlockId &&\n            nestedBlockParent.has(rCompleteBlockId) &&\n            nestedBlockMessages.has(rCompleteBlockId)\n          ) {\n            const nested = nestedBlockMessages.get(rCompleteBlockId)!;\n            if (nested.reasoning) {\n              const nestedReflection =\n                typeof payload.text === \"string\" ? payload.text : \"\";\n              if (nestedReflection && nested.reasoning.chunks.length === 0) {\n                nested.reasoning.chunks.push(nestedReflection);\n              }\n              nested.reasoning.status = \"complete\";\n              nested.streaming = false;\n              emitMessage(nested);\n            }\n            nestedBlockParent.delete(rCompleteBlockId);\n            nestedBlockMessages.delete(rCompleteBlockId);\n            continue;\n          }\n          const reasoningId =\n            resolveReasoningId(payload, false) ??\n            resolveReasoningId(payload, true) ??\n            `reason-${nextSequence()}`;\n          // A close carrying text (or scope:\"loop\") is a cross-iteration\n          // reflection fold (merged spec §4 E3): the API streams nothing for the\n          // block, then delivers the whole reflection as `text` on the close.\n          // Materialize a reasoning bubble even if no reasoning_start/delta opened\n          // one, and adopt the close text when the block streamed no chunks (the\n          // common reflection case, where reasoning_start opened an empty bubble).\n          const reflectionText = typeof payload.text === \"string\" ? payload.text : \"\";\n          if (!reasoningMessages.get(reasoningId) && (reflectionText || payload.scope === \"loop\")) {\n            ensureReasoningMessage(reasoningId);\n          }\n          const reasoningMessage = reasoningMessages.get(reasoningId);\n          if (reasoningMessage?.reasoning) {\n            if (payload.scope === \"loop\" || payload.scope === \"turn\") {\n              reasoningMessage.reasoning.scope = payload.scope;\n            }\n            if (reflectionText && reasoningMessage.reasoning.chunks.length === 0) {\n              reasoningMessage.reasoning.chunks.push(reflectionText);\n            }\n            reasoningMessage.reasoning.status = \"complete\";\n            reasoningMessage.reasoning.completedAt = resolveTimestamp(\n              payload.completedAt ?? payload.timestamp\n            );\n            const start = reasoningMessage.reasoning.startedAt ?? Date.now();\n            reasoningMessage.reasoning.durationMs = Math.max(\n              0,\n              (reasoningMessage.reasoning.completedAt ?? Date.now()) - start\n            );\n            reasoningMessage.streaming = false;\n\n            emitMessage(reasoningMessage);\n          }\n          const stepKey = getStepKey(payload);\n          if (stepKey) {\n            reasoningContext.byStep.delete(stepKey);\n          }\n        } else if (payloadType === \"tool_start\") {\n          // Unified tool family (agent + flow). Seal any open assistant bubble so\n          // text→tool→text interleaves chronologically (the API also emits a\n          // text_complete here, so this is usually a no-op — kept for safety).\n          if (assistantMessage) {\n            (assistantMessage as AgentWidgetMessage).streaming = false;\n            emitMessage(assistantMessage as AgentWidgetMessage);\n            assistantMessage = null;\n          }\n          // Unified denormalizes `iteration` onto tool frames too (merged spec §2).\n          // Track it so media/reflection blocks — which carry no iteration of their\n          // own — can be stamped with the enclosing iteration even on tool-only\n          // turns that never emit a `turn_start`.\n          if (typeof payload.iteration === \"number\") lastIterationSeen = payload.iteration;\n          const toolId: string =\n            (typeof payload.toolCallId === \"string\" ? payload.toolCallId : undefined) ??\n            resolveToolId(payload, true) ??\n            `tool-${nextSequence()}`;\n          const toolName = payload.toolName ?? payload.name;\n          // Suppress tool UI for artifact emit tools: artifacts are handled via artifact_* events\n          if (isArtifactEmitToolName(toolName)) {\n            artifactToolCallIds.add(toolId);\n            continue;\n          }\n          trackToolId(getToolCallKey(payload), toolId);\n          const toolMessage = ensureToolMessage(toolId);\n          const tool = toolMessage.toolCall ?? {\n            id: toolId,\n            status: \"pending\"\n          };\n          tool.name = toolName ?? tool.name;\n          tool.status = \"running\";\n          if (payload.parameters !== undefined) {\n            tool.args = payload.parameters;\n          } else if (payload.args !== undefined) {\n            tool.args = payload.args;\n          }\n          tool.startedAt =\n            tool.startedAt ??\n            resolveTimestamp(payload.startedAt ?? payload.timestamp);\n          tool.completedAt = undefined;\n          tool.durationMs = undefined;\n          toolMessage.toolCall = tool;\n          toolMessage.streaming = true;\n          if (payload.executionId) {\n            toolMessage.agentMetadata = {\n              executionId: payload.executionId,\n              iteration: payload.iteration,\n            };\n          }\n          emitMessage(toolMessage);\n        } else if (payloadType === \"tool_output_delta\") {\n          const toolId =\n            resolveToolId(payload, false) ??\n            resolveToolId(payload, true) ??\n            `tool-${nextSequence()}`;\n          if (artifactToolCallIds.has(toolId)) continue;\n          const toolMessage = ensureToolMessage(toolId);\n          const tool = toolMessage.toolCall ?? {\n            id: toolId,\n            status: \"running\"\n          };\n          tool.startedAt =\n            tool.startedAt ??\n            resolveTimestamp(payload.startedAt ?? payload.timestamp);\n          const chunkText =\n            payload.text ?? payload.delta ?? payload.message ?? \"\";\n          if (chunkText) {\n            tool.chunks = tool.chunks ?? [];\n            tool.chunks.push(String(chunkText));\n          }\n          tool.status = \"running\";\n          toolMessage.toolCall = tool;\n          toolMessage.streaming = true;\n          const agentCtxChunk = payload.agentContext;\n          if (agentCtxChunk || payload.executionId) {\n            toolMessage.agentMetadata = toolMessage.agentMetadata ?? {\n              executionId: agentCtxChunk?.executionId ?? payload.executionId,\n              iteration: agentCtxChunk?.iteration ?? payload.iteration,\n            };\n          }\n          emitMessage(toolMessage);\n        } else if (payloadType === \"tool_complete\") {\n          const toolId =\n            resolveToolId(payload, false) ??\n            resolveToolId(payload, true) ??\n            `tool-${nextSequence()}`;\n          if (artifactToolCallIds.has(toolId)) {\n            artifactToolCallIds.delete(toolId);\n            continue;\n          }\n          const toolMessage = ensureToolMessage(toolId);\n          const tool = toolMessage.toolCall ?? {\n            id: toolId,\n            status: \"running\"\n          };\n          tool.status = \"complete\";\n          if (payload.result !== undefined) {\n            tool.result = payload.result;\n          }\n          if (typeof payload.duration === \"number\") {\n            tool.duration = payload.duration;\n          }\n          tool.completedAt = resolveTimestamp(\n            payload.completedAt ?? payload.timestamp\n          );\n          const durationValue = payload.duration ?? payload.executionTime;\n          if (typeof durationValue === \"number\") {\n            tool.durationMs = durationValue;\n          } else {\n            const start = tool.startedAt ?? Date.now();\n            tool.durationMs = Math.max(\n              0,\n              (tool.completedAt ?? Date.now()) - start\n            );\n          }\n          toolMessage.toolCall = tool;\n          toolMessage.streaming = false;\n          const agentCtxComplete = payload.agentContext;\n          if (agentCtxComplete || payload.executionId) {\n            toolMessage.agentMetadata = toolMessage.agentMetadata ?? {\n              executionId: agentCtxComplete?.executionId ?? payload.executionId,\n              iteration: agentCtxComplete?.iteration ?? payload.iteration,\n            };\n          }\n          emitMessage(toolMessage);\n          const callKey = getToolCallKey(payload);\n          if (callKey) {\n            toolContext.byCall.delete(callKey);\n          }\n        } else if (payloadType === \"await\" && payload.toolName) {\n          // LOCAL tool pause. Two wire shapes resolve here, by dispatch target:\n          //  - FLOW dispatch → `step_await` + `awaitReason: \"local_tool_required\"`\n          //    (Runtype's prompt step throws LocalToolRequiredError when the model\n          //    calls a `toolType: \"local\"` tool).\n          //  - AGENT dispatch → `agent_await` (the agent runtime's native pause).\n          // Either way the server emits the tool name, params, and execution id;\n          // the execution pauses until the client POSTs /resume with toolOutputs.\n          // `agent_await` carries a BARE tool name plus an `origin`; page tools\n          // (origin \"webmcp\") are normalized to the `webmcp:`-prefixed form below\n          // so the bridge + session.ts `/resume` keying are identical for both.\n          //\n          // Upsert a fully-populated tool-variant message so the existing\n          // ask_user_question bubble + sheet paths fire. Mark the message with\n          // `awaitingLocalTool: true` so the UI knows to resolve via\n          // resumeFlow rather than the legacy sendMessage fallback.\n          //\n          // Key the message by the per-call `toolCallId` (provider `toolu_…`;\n          // core#3878) when present. Two PARALLEL calls to the SAME tool in one\n          // turn collapse to an identical `toolId` (`runtime_webmcp:<name>_<ms>`)\n          // and `index: 0`: only `toolCallId` distinguishes them. Keying on it\n          // (a) keeps the two awaits as DISTINCT messages with their own args\n          // instead of the second clobbering the first, and (b) merges each\n          // await into the matching `tool_start` bubble (also keyed by\n          // `toolCallId`). Fall back to the collapsed `toolId` for legacy\n          // servers that don't emit `toolCallId`.\n          const toolCallId: string | undefined =\n            typeof payload.toolCallId === \"string\" && payload.toolCallId.length > 0\n              ? (payload.toolCallId as string)\n              : undefined;\n          const toolId =\n            toolCallId ?? (payload.toolId as string) ?? `local-${nextSequence()}`;\n          const toolMessage = ensureToolMessage(toolId);\n          const rawToolName = payload.toolName as string;\n          // `agent_await` page tools arrive with a bare name; synthesize the\n          // `webmcp:` prefix so isWebMcpToolName (and the bridge's prefix-strip on\n          // resume) treat them identically to a flow `step_await`.\n          const toolName =\n            payload.origin === \"webmcp\" &&\n            !isWebMcpToolName(rawToolName)\n              ? `webmcp:${rawToolName}`\n              : rawToolName;\n          const webMcpTool = isWebMcpToolName(toolName);\n          const tool = toolMessage.toolCall ?? { id: toolId, status: \"pending\" as const };\n          tool.name = toolName;\n          tool.args = payload.parameters;\n          // WebMCP tools are executed asynchronously by the browser AFTER this\n          // `step_await` arrives. Keep them running until session.ts resolves\n          // the page tool and records its actual elapsed time. Other local\n          // tools (for example ask_user_question) keep the existing complete\n          // state because they are waiting for a user interaction, not an\n          // automatic page-tool execution.\n          tool.status = webMcpTool ? \"running\" : \"complete\";\n          tool.chunks = tool.chunks ?? [];\n          tool.startedAt =\n            tool.startedAt ??\n            resolveTimestamp(payload.startedAt ?? payload.timestamp ?? payload.awaitedAt);\n          if (webMcpTool) {\n            tool.completedAt = undefined;\n            tool.duration = undefined;\n            tool.durationMs = undefined;\n          } else {\n            tool.completedAt = tool.completedAt ?? tool.startedAt;\n          }\n          toolMessage.toolCall = tool;\n          toolMessage.streaming = false;\n          toolMessage.agentMetadata = {\n            ...toolMessage.agentMetadata,\n            executionId: (payload.executionId as string) ?? toolMessage.agentMetadata?.executionId,\n            awaitingLocalTool: true,\n            // Only set when the server emitted a real per-call id; its presence\n            // is what tells session.ts to batch + key `/resume` by id rather\n            // than by tool name (which can't represent two same-tool calls).\n            ...(toolCallId ? { webMcpToolCallId: toolCallId } : {}),\n          };\n          emitMessage(toolMessage);\n        } else if (payloadType === \"text_start\") {\n          // Nested flow-as-tool text (PR #4602): a `parentToolCallId` means this\n          // block belongs to a flow running as that tool — record the mapping and\n          // leave the top-level assistant bubble untouched (the nested deltas route\n          // into the parent tool's row).\n          const startBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          const startParent =\n            typeof payload.parentToolCallId === \"string\" && payload.parentToolCallId\n              ? payload.parentToolCallId\n              : null;\n          if (startBlockId && startParent) {\n            nestedBlockParent.set(startBlockId, startParent);\n            continue;\n          }\n          // Unified text-channel block open. A new block id means a new bubble, so\n          // seal any open assistant bubble; the next text_delta creates a fresh one\n          // (lazily). The API emits a fresh block at every tool/media/approval/await\n          // boundary, so block-id keying drives segmentation — no partId.\n          const prev = assistantMessage as AgentWidgetMessage | null;\n          if (prev) {\n            // Normally text_complete already sealed the prior block; this is the\n            // defensive path if a producer opens a new block without closing.\n            if (executionKind === \"flow\") {\n              finalizeFlowTextBlock(prev);\n              lastSealedFlowBubble = prev;\n            } else {\n              prev.streaming = false;\n              emitMessage(prev);\n            }\n            assistantMessage = null;\n          }\n          currentTextBlockId =\n            typeof payload.id === \"string\" ? payload.id : currentTextBlockId;\n          pendingFlowRaw = \"\";\n        } else if (payloadType === \"text_delta\") {\n          // Nested flow-as-tool text: route to the parent tool's row, through the\n          // same structured-content parser, never the top-level assistant channel.\n          const deltaBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          const nestedParent = deltaBlockId\n            ? nestedBlockParent.get(deltaBlockId)\n            : undefined;\n          if (deltaBlockId && nestedParent) {\n            const nestedDelta =\n              typeof payload.delta === \"string\" ? payload.delta : \"\";\n            const nestedRaw = (nestedBlockRaw.get(deltaBlockId) ?? \"\") + nestedDelta;\n            nestedBlockRaw.set(deltaBlockId, nestedRaw);\n            if (nestedRaw.trim() === \"\") continue;\n            const nested = ensureNestedBlockMessage(deltaBlockId, nestedParent);\n            nested.agentMetadata = {\n              ...nested.agentMetadata,\n              executionId: payload.executionId,\n              parentToolId: nestedParent,\n            };\n            applyFlowTextChunk(nested, nestedRaw, nestedDelta, undefined);\n            continue;\n          }\n          currentTextBlockId =\n            typeof payload.id === \"string\" ? payload.id : currentTextBlockId;\n          if (executionKind === \"flow\") {\n            // Flow prompt-step text can be structured JSON: accumulate the raw\n            // block and run it through the structured-content parser, keyed by the\n            // block-id bubble. Materialize lazily so a whitespace-only block\n            // (newlines around a tool boundary) never leaves a stray bubble.\n            const delta = typeof payload.delta === \"string\" ? payload.delta : \"\";\n            pendingFlowRaw += delta;\n            if (pendingFlowRaw.trim() === \"\") continue;\n            const assistant = ensureAssistantMessage();\n            assistant.agentMetadata = {\n              executionId: payload.executionId,\n              iteration: payload.iteration,\n            };\n            applyFlowTextChunk(assistant, pendingFlowRaw, delta, undefined);\n            lastAssistantInTurn = assistant;\n            continue;\n          }\n          const assistant = ensureAssistantMessage();\n          assistant.content += payload.delta ?? '';\n          assistant.agentMetadata = {\n            executionId: payload.executionId,\n            iteration: payload.iteration,\n            turnId: openTurnId ?? undefined,\n            agentName: agentExecution?.agentName\n          };\n          lastAssistantInTurn = assistant;\n          emitMessage(assistant);\n        } else if (payloadType === \"text_complete\") {\n          // Nested flow-as-tool text block close: seal its parent-tool-row message.\n          const completeBlockId = typeof payload.id === \"string\" ? payload.id : null;\n          if (completeBlockId && nestedBlockParent.has(completeBlockId)) {\n            const nested = nestedBlockMessages.get(completeBlockId);\n            if (nested) finalizeFlowTextBlock(nested);\n            nestedBlockParent.delete(completeBlockId);\n            nestedBlockRaw.delete(completeBlockId);\n            nestedBlockMessages.delete(completeBlockId);\n            continue;\n          }\n          // Seal the current text block's bubble.\n          const prev = assistantMessage as AgentWidgetMessage | null;\n          if (prev) {\n            if (executionKind === \"flow\") {\n              // Final structured extraction off the accumulated raw buffer; the\n              // authoritative step_complete.result.response reconciles next.\n              finalizeFlowTextBlock(prev);\n              lastSealedFlowBubble = prev;\n            } else {\n              // U2: text_complete carries the assembled text, but the bubble already\n              // holds it from the deltas — only fall back to payload.text when no\n              // delta content was seen, never double-count.\n              if ((prev.content ?? \"\") === \"\" && typeof payload.text === \"string\") {\n                prev.content = payload.text;\n              }\n              prev.streaming = false;\n              emitMessage(prev);\n            }\n            assistantMessage = null;\n          }\n          currentTextBlockId = null;\n          pendingFlowRaw = \"\";\n        } else if (payloadType === \"step_complete\") {\n          // Only process completions for prompt steps, not tool/context steps\n          const stepType = (payload as any).stepType;\n          const executionType = (payload as any).executionType;\n          if (stepType === \"tool\" || executionType === \"context\") {\n            // Skip tool-related completions - they're handled by tool_complete\n            continue;\n          }\n\n          // A failed step (`success:false`) — including the legacy `step_error`\n          // event, which the wire encoder folds into a failed `step_complete`\n          // — surfaces as a terminal error and finalizes the stream.\n          if (payload.success === false) {\n            const e = payload.error;\n            const message =\n              typeof e === \"string\" && e !== \"\"\n                ? e\n                : // Reflect.has, not `in`: the `in` operator inside an arrow body can\n                  // be minified into a `for(init;;)` head, which Oxc mis-parses and\n                  // Rolldown (Vite 8) silently emits as an empty chunk. Same\n                  // [[HasProperty]] semantics. Enforced by scripts/check-dist-no-in-for-init.mjs.\n                  e != null && typeof e === \"object\" && Reflect.has(e, \"message\")\n                  ? String((e as { message?: unknown }).message ?? \"Step failed\")\n                  : \"Step failed\";\n            onEvent({ type: \"error\", error: new Error(message) });\n            const finalMsg = assistantMessage as AgentWidgetMessage | null;\n            if (finalMsg && finalMsg.streaming) {\n              finalMsg.streaming = false;\n              emitMessage(finalMsg);\n            }\n            onEvent({ type: \"status\", status: \"idle\" });\n            continue;\n          }\n\n          // Unified flow: reconcile the just-sealed text block with the\n          // authoritative structured final (`result.response`). Displayed content\n          // stays as streamed — a multi-segment step keeps each bubble's own text;\n          // reconcile only fills/repairs the last sealed block and sets rawContent.\n          // A pure-tool / text-less step (no sealed flow bubble) completes silently.\n          {\n            const sealed = lastSealedFlowBubble;\n            lastSealedFlowBubble = null;\n            const flowStopReason = (payload as any).stopReason as\n              | StopReasonKind\n              | undefined;\n            const finalResponse = payload.result?.response;\n            if (sealed) {\n              if (flowStopReason) sealed.stopReason = flowStopReason;\n              if (finalResponse !== undefined && finalResponse !== null) {\n                reconcileSealedAssistantWithFinalResponse(sealed, finalResponse);\n              } else if (sealed.streaming !== false) {\n                streamParsers.delete(sealed.id);\n                rawContentBuffers.delete(sealed.id);\n                sealed.streaming = false;\n                emitMessage(sealed);\n              }\n            } else {\n              // Buffered / dispatch-mode step: no streamed text block, but the step\n              // carries the final response (and/or a stopReason) — render it as the\n              // assistant message. An empty response + stopReason still surfaces a\n              // sealed bubble so the UI can show an affordance.\n              const hasResponse =\n                finalResponse !== undefined &&\n                finalResponse !== null &&\n                finalResponse !== \"\";\n              if (hasResponse || flowStopReason) {\n                const assistant = ensureAssistantMessage();\n                if (flowStopReason) assistant.stopReason = flowStopReason;\n                if (hasResponse) {\n                  finalizeFlowTextBlock(assistant, finalResponse);\n                } else {\n                  assistant.streaming = false;\n                  emitMessage(assistant);\n                }\n              }\n            }\n            continue;\n          }\n        // ================================================================\n        // Agent Loop Execution Events\n        // ================================================================\n        } else if (payloadType === \"execution_start\") {\n          executionKind = payload.kind === \"flow\" ? \"flow\" : \"agent\";\n          executionKindResolved = true;\n          if (executionKind === \"agent\") {\n            agentExecution = {\n              executionId: payload.executionId,\n              agentId: payload.agentId ?? 'virtual',\n              agentName: payload.agentName ?? '',\n              status: 'running',\n              currentIteration: 0,\n              maxTurns: payload.maxTurns ?? 1,\n              startedAt: resolveTimestamp(payload.startedAt)\n            };\n          }\n        } else if (payloadType === \"turn_start\") {\n          // Unified collapsed `agent_iteration_*` into a denormalized `iteration`\n          // field on the turn (merged spec §2). Reconstruct the per-iteration\n          // message boundary the 'separate' renderer keys off: when the iteration\n          // advances, seal the previous iteration's bubble and rotate to a new one.\n          const iteration =\n            typeof payload.iteration === \"number\" ? payload.iteration : lastIterationSeen;\n          if (iteration !== lastIterationSeen) {\n            if (agentExecution) agentExecution.currentIteration = iteration;\n            if (iterationDisplay === 'separate' && iteration > 1) {\n              const prevMsg = assistantMessage as AgentWidgetMessage | null;\n              if (prevMsg) {\n                prevMsg.streaming = false;\n                emitMessage(prevMsg);\n                agentIterationMessages.set(iteration - 1, prevMsg);\n                assistantMessage = null;\n              }\n            }\n            lastIterationSeen = iteration;\n          }\n          openTurnId = typeof payload.id === \"string\" ? payload.id : null;\n          // Reset the per-turn assistant tracker. lastAssistantInTurn is used by\n          // turn_complete to attach stopReason to the final text segment of the\n          // turn even if that segment was sealed by an intervening tool boundary.\n          lastAssistantInTurn = null;\n        } else if (payloadType === \"tool_input_delta\") {\n          // Streamed tool arguments (display-only; authoritative args ride\n          // tool_input_complete / tool_start).\n          const toolId = payload.toolCallId ?? toolContext.lastId;\n          if (toolId) {\n            const toolMessage = toolMessages.get(toolId);\n            if (toolMessage?.toolCall) {\n              toolMessage.toolCall.chunks = toolMessage.toolCall.chunks ?? [];\n              toolMessage.toolCall.chunks.push(payload.delta ?? '');\n              emitMessage(toolMessage);\n            }\n          }\n        } else if (payloadType === \"tool_input_complete\") {\n          // Authoritative args are set at tool_start; nothing to render here.\n          continue;\n        } else if (payloadType === \"turn_complete\") {\n          // Reasoning is sealed by its own reasoning_complete on the wire\n          // vocabulary; this only attaches the turn-level stopReason to the\n          // assistant message produced by this turn. Falls back to\n          // lastAssistantInTurn when the bubble was sealed at a tool boundary\n          // mid-turn, so the notice still attaches to the final visible segment.\n          const turnStopReason = (payload as any).stopReason as\n            | StopReasonKind\n            | undefined;\n          const stopReasonTarget = assistantMessage ?? lastAssistantInTurn;\n          if (turnStopReason && stopReasonTarget !== null) {\n            const turnId = payload.id;\n            const matchesTurn =\n              !turnId || stopReasonTarget.agentMetadata?.turnId === turnId;\n            if (matchesTurn) {\n              stopReasonTarget.stopReason = turnStopReason;\n              emitMessage(stopReasonTarget);\n            }\n          }\n          if (openTurnId === payload.id) openTurnId = null;\n        } else if (payloadType === \"media_start\") {\n          // Open a media block; buffer fragments until media_complete.\n          const id = String(payload.id);\n          mediaBuffers.set(id, {\n            mediaType: typeof payload.mediaType === \"string\" ? payload.mediaType : undefined,\n            role: typeof payload.role === \"string\" ? payload.role : undefined,\n            toolCallId: payload.toolCallId,\n            parts: [],\n          });\n        } else if (payloadType === \"media_delta\") {\n          const buf = mediaBuffers.get(String(payload.id));\n          if (buf && typeof payload.delta === \"string\") buf.parts.push(payload.delta);\n        } else if (payloadType === \"media_complete\") {\n          // Reassemble the buffered media triad into a single AI SDK–aligned\n          // `MediaContentPart`, then render it as a synthetic assistant message\n          // inserted between the tool bubble and the next text turn:\n          //   { type: 'media', data, mediaType }                // AI SDK v6: base64\n          //   { type: 'image-url', url, mediaType? }            // AI SDK v3/v4\n          //   { type: 'file-url', url, mediaType }              // AI SDK v3/v4\n          const mediaBlockId = String(payload.id);\n          const buf = mediaBuffers.get(mediaBlockId);\n          mediaBuffers.delete(mediaBlockId);\n          const completeMediaType =\n            (typeof payload.mediaType === \"string\" ? payload.mediaType : undefined) ??\n            buf?.mediaType ??\n            \"application/octet-stream\";\n          const completeData = typeof payload.data === \"string\" ? payload.data : undefined;\n          const completeUrl =\n            typeof payload.url === \"string\"\n              ? payload.url\n              : buf && buf.parts.length > 0\n                ? buf.parts.join(\"\")\n                : undefined;\n          let reconstructed: Record<string, unknown> | null = null;\n          if (completeData) {\n            reconstructed = { type: \"media\", data: completeData, mediaType: completeMediaType };\n          } else if (completeUrl) {\n            // The wire is mediaType-only; a URL part with no declared MIME\n            // arrives as the bare bucket hint \"image\" (per the API encoder). Treat\n            // that — and any real `image/*` — as a hosted image so we don't misroute\n            // generated images into the file bucket.\n            const lower = completeMediaType.toLowerCase();\n            const isImage = lower === \"image\" || lower.startsWith(\"image/\");\n            reconstructed = {\n              type: isImage ? \"image-url\" : \"file-url\",\n              url: completeUrl,\n              mediaType: completeMediaType,\n            };\n          }\n          const mediaToolCallId = payload.toolCallId ?? buf?.toolCallId;\n          const rawMedia = reconstructed ? [reconstructed] : [];\n          const mediaContentParts: ContentPart[] = [];\n          for (const part of rawMedia) {\n            if (!part || typeof part !== \"object\") continue;\n            const rec = part as Record<string, unknown>;\n            const partType = typeof rec.type === \"string\" ? rec.type : undefined;\n\n            // Resolve `(src, mediaType)` for the part.\n            // RFC 7231 says MIME types are case-insensitive, so we canonicalize\n            // to lowercase once here. That makes the `startsWith(\"image/\")` /\n            // `\"audio/\"` / `\"video/\"` bucket checks robust to upstream tools\n            // that emit non-canonical casing like `Image/PNG`.\n            const rawMediaType =\n              typeof rec.mediaType === \"string\" ? rec.mediaType.toLowerCase() : \"\";\n            let src: string | null = null;\n            let mediaType = \"\";\n            if (partType === \"media\") {\n              const data = typeof rec.data === \"string\" ? rec.data : undefined;\n              if (!data) continue;\n              // Empty/missing mediaType yields `data:;base64,...` which RFC 2397\n              // resolves to `text/plain`: stamp a default so the data URI is\n              // well-formed and the part lands in the file bucket.\n              mediaType = rawMediaType.length > 0 ? rawMediaType : \"application/octet-stream\";\n              src = `data:${mediaType};base64,${data}`;\n            } else if (partType === \"image-url\") {\n              const url = typeof rec.url === \"string\" ? rec.url : undefined;\n              if (!url) continue;\n              mediaType = rawMediaType;\n              src = url;\n            } else if (partType === \"file-url\") {\n              const url = typeof rec.url === \"string\" ? rec.url : undefined;\n              if (!url) continue;\n              mediaType = rawMediaType;\n              src = url;\n            } else {\n              continue;\n            }\n            if (!src) continue;\n\n            // Pick the right rendering bucket based on mediaType.\n            if (partType === \"image-url\" || mediaType.startsWith(\"image/\")) {\n              mediaContentParts.push({\n                type: \"image\",\n                image: src,\n                // Only a real MIME (`image/png`) is a usable mimeType; the bare\n                // bucket hint \"image\" (a hosted URL with no declared type) is not.\n                ...(mediaType.includes(\"/\") ? { mimeType: mediaType } : {}),\n              });\n            } else if (mediaType.startsWith(\"audio/\")) {\n              mediaContentParts.push({\n                type: \"audio\",\n                audio: src,\n                mimeType: mediaType,\n              });\n            } else if (mediaType.startsWith(\"video/\")) {\n              mediaContentParts.push({\n                type: \"video\",\n                video: src,\n                mimeType: mediaType,\n              });\n            } else {\n              const resolvedMediaType = mediaType || \"application/octet-stream\";\n              mediaContentParts.push({\n                type: \"file\",\n                data: src,\n                mimeType: resolvedMediaType,\n                filename: filenameFromMediaType(resolvedMediaType),\n              });\n            }\n          }\n\n          if (mediaContentParts.length > 0) {\n            // Uniquify per emission. A tool may emit multiple `agent_media`\n            // events for the same `toolCallId` (e.g. streamed/batched media);\n            // sharing an id would let `emitMessage` merge them by id and\n            // overwrite the prior `contentParts`.\n            const seq = nextSequence();\n            const toolCallIdRaw = mediaToolCallId;\n            const mediaIdSuffix =\n              typeof toolCallIdRaw === \"string\" && toolCallIdRaw.length > 0\n                ? `${toolCallIdRaw}-${seq}`\n                : String(seq);\n            const mediaMessage: AgentWidgetMessage = {\n              id: `agent-media-${mediaIdSuffix}`,\n              role: \"assistant\",\n              content: \"\",\n              contentParts: mediaContentParts,\n              createdAt: new Date().toISOString(),\n              streaming: false,\n              sequence: seq,\n              agentMetadata: {\n                executionId: payload.executionId,\n                // Media blocks carry no iteration of their own; stamp the\n                // enclosing iteration tracked from turn/tool frames.\n                iteration:\n                  typeof payload.iteration === \"number\"\n                    ? payload.iteration\n                    : lastIterationSeen,\n              },\n            };\n            emitMessage(mediaMessage);\n\n            // Seal any in-flight assistant text bubble before splitting the\n            // stream. Without this, an orphan bubble retains `streaming: true`\n            // forever: `agent_complete` only finalizes the latest\n            // `assistantMessage`, so the typing/caret indicator would stay on\n            // the prior bubble even though no more deltas will arrive.\n            const prevAssistant = assistantMessage as AgentWidgetMessage | null;\n            if (prevAssistant) {\n              prevAssistant.streaming = false;\n              emitMessage(prevAssistant);\n            }\n            assistantMessage = null;\n            assistantMessageRef.current = null;\n          }\n        } else if (payloadType === \"execution_complete\") {\n          const kind = payload.kind ?? executionKind;\n          if (kind === \"agent\" && agentExecution) {\n            agentExecution.status = payload.success ? 'complete' : 'error';\n            agentExecution.completedAt = resolveTimestamp(payload.completedAt);\n            agentExecution.stopReason = payload.stopReason;\n          }\n\n          // Finalize any still-open assistant message. Per-step reconciliation\n          // (step_complete.result.response) normally sealed the flow blocks\n          // already; this is the defensive close for an unterminated block, and\n          // for flow it runs the final structured extraction off the raw buffer.\n          const finalMsg = assistantMessage as AgentWidgetMessage | null;\n          if (finalMsg) {\n            if (kind === \"flow\" && finalMsg.streaming !== false) {\n              finalizeFlowTextBlock(finalMsg);\n            } else {\n              finalMsg.streaming = false;\n              emitMessage(finalMsg);\n            }\n            assistantMessage = null;\n          }\n          currentTextBlockId = null;\n          pendingFlowRaw = \"\";\n          lastSealedFlowBubble = null;\n\n          // `terminal: true` marks this as a graceful finish (not a drop). The\n          // session uses it to distinguish the real end-of-turn from the plain\n          // `idle` the dispatch wrappers emit in their `finally` when a durable\n          // connection drops mid-stream (durable-reconnect drop detection).\n          onEvent({ type: \"status\", status: \"idle\", terminal: true });\n        } else if (payloadType === \"execution_error\") {\n          // Terminal failure. The non-terminal `error` is handled\n          // separately (recoverable → warn).\n          const errorMessage = typeof payload.error === 'string'\n            ? payload.error\n            : payload.error?.message ?? 'Execution error';\n          onEvent({\n            type: \"error\",\n            error: new Error(errorMessage)\n          });\n        } else if (payloadType === \"ping\") {\n          // Keep-alive heartbeat - no action needed\n        // ================================================================\n        // Tool Approval Events\n        // ================================================================\n        } else if (payloadType === \"approval_start\") {\n          const approvalId = payload.approvalId ?? `approval-${nextSequence()}`;\n          const approvalMessage: AgentWidgetMessage = {\n            id: `approval-${approvalId}`,\n            role: \"assistant\",\n            content: \"\",\n            createdAt: new Date().toISOString(),\n            streaming: false,\n            variant: \"approval\",\n            sequence: nextSequence(),\n            approval: {\n              id: approvalId,\n              status: \"pending\",\n              agentId: agentExecution?.agentId ?? 'virtual',\n              executionId: payload.executionId ?? agentExecution?.executionId ?? '',\n              toolName: payload.toolName ?? '',\n              toolType: payload.toolType,\n              description: payload.description ?? `Execute ${payload.toolName ?? 'tool'}`,\n              ...(typeof payload.reason === \"string\" && payload.reason\n                ? { reason: payload.reason }\n                : {}),\n              parameters: payload.parameters,\n            },\n          };\n          emitMessage(approvalMessage);\n        } else if (payloadType === \"step_await\" && payload.awaitReason === \"approval_required\") {\n          const approvalId = payload.approvalId ?? `approval-${nextSequence()}`;\n          const approvalMessage: AgentWidgetMessage = {\n            id: `approval-${approvalId}`,\n            role: \"assistant\",\n            content: \"\",\n            createdAt: new Date().toISOString(),\n            streaming: false,\n            variant: \"approval\",\n            sequence: nextSequence(),\n            approval: {\n              id: approvalId,\n              status: \"pending\",\n              agentId: agentExecution?.agentId ?? 'virtual',\n              executionId: payload.executionId ?? agentExecution?.executionId ?? '',\n              toolName: payload.toolName ?? '',\n              toolType: payload.toolType,\n              description: payload.description ?? `Execute ${payload.toolName ?? 'tool'}`,\n              ...(typeof payload.reason === \"string\" && payload.reason\n                ? { reason: payload.reason }\n                : {}),\n              parameters: payload.parameters,\n            },\n          };\n          emitMessage(approvalMessage);\n        } else if (payloadType === \"approval_complete\") {\n          const approvalId = payload.approvalId;\n          if (approvalId) {\n            // Find and update the existing approval message\n            const approvalMessageId = `approval-${approvalId}`;\n            const existingMessage: AgentWidgetMessage = {\n              id: approvalMessageId,\n              role: \"assistant\",\n              content: \"\",\n              createdAt: new Date().toISOString(),\n              streaming: false,\n              variant: \"approval\",\n              sequence: nextSequence(),\n              approval: {\n                id: approvalId,\n                status: (payload.decision as \"approved\" | \"denied\") ?? \"approved\",\n                agentId: agentExecution?.agentId ?? 'virtual',\n                executionId: payload.executionId ?? agentExecution?.executionId ?? '',\n                toolName: payload.toolName ?? '',\n                description: payload.description ?? '',\n                resolvedAt: Date.now(),\n              },\n            };\n            emitMessage(existingMessage);\n          }\n        } else if (\n          payloadType === \"artifact_start\" ||\n          payloadType === \"artifact_delta\" ||\n          payloadType === \"artifact_update\" ||\n          payloadType === \"artifact_complete\"\n        ) {\n          if (payloadType === \"artifact_start\") {\n            const at = payload.artifactType as PersonaArtifactKind;\n            const artId = String(payload.id);\n            const artTitle = typeof payload.title === \"string\" ? payload.title : undefined;\n            // Additive `file` metadata: validate shape (object with string path +\n            // string mimeType; optional string language). Drop silently if malformed\n            // so old/new backends and unexpected payloads never break the stream.\n            const rawFile = payload.file;\n            let artFile: PersonaArtifactFileMeta | undefined;\n            if (\n              rawFile &&\n              typeof rawFile === \"object\" &&\n              !Array.isArray(rawFile) &&\n              typeof rawFile.path === \"string\" &&\n              typeof rawFile.mimeType === \"string\"\n            ) {\n              artFile = {\n                path: rawFile.path,\n                mimeType: rawFile.mimeType,\n                ...(typeof rawFile.language === \"string\" ? { language: rawFile.language } : {}),\n              };\n            }\n            onEvent({\n              type: \"artifact_start\",\n              id: artId,\n              artifactType: at,\n              title: artTitle,\n              component: typeof payload.component === \"string\" ? payload.component : undefined,\n              ...(artFile ? { file: artFile } : {})\n            });\n            // Seed component props from artifact_start when the payload\n            // carries them; artifact_update events accumulate the rest below.\n            const startProps =\n              payload.props && typeof payload.props === \"object\" && !Array.isArray(payload.props)\n                ? { ...(payload.props as Record<string, unknown>) }\n                : undefined;\n            artifactContent.set(artId, {\n              markdown: \"\",\n              title: artTitle,\n              file: artFile,\n              ...(startProps ? { props: startProps } : {})\n            });\n            // Insert the in-thread artifact block (skip if already present from\n            // transcript_insert). The resolved display mode picks the component:\n            // \"card\"/\"panel\" inject the reference card; \"inline\" injects the\n            // inline preview block. Both share the rawContent JSON-component\n            // shape so transcript persistence and hydration work unchanged.\n            if (!artifactIdsWithCards.has(artId)) {\n              artifactIdsWithCards.add(artId);\n              const displayMode = resolveArtifactDisplayMode(\n                this.config.features?.artifacts,\n                at\n              );\n              const artComponent =\n                typeof payload.component === \"string\" ? payload.component : undefined;\n              const cardMsg: AgentWidgetMessage = {\n                id: `artifact-ref-${artId}`,\n                role: \"assistant\",\n                content: \"\",\n                createdAt: new Date().toISOString(),\n                streaming: true,\n                sequence: nextSequence(),\n                rawContent: buildArtifactRefRawContent(displayMode, {\n                  artifactId: artId,\n                  title: artTitle,\n                  artifactType: at,\n                  status: \"streaming\",\n                  ...(artFile ? { file: artFile } : {}),\n                  ...(artComponent ? { component: artComponent } : {}),\n                }),\n              };\n              artifactCardMessages.set(artId, cardMsg);\n              emitMessage(cardMsg);\n            }\n          } else if (payloadType === \"artifact_delta\") {\n            const deltaId = String(payload.id);\n            const deltaText = typeof payload.delta === \"string\" ? payload.delta : String(payload.delta ?? \"\");\n            onEvent({\n              type: \"artifact_delta\",\n              id: deltaId,\n              artDelta: deltaText\n            });\n            const acc = artifactContent.get(deltaId);\n            if (acc) acc.markdown += deltaText;\n          } else if (payloadType === \"artifact_update\") {\n            const props =\n              payload.props && typeof payload.props === \"object\" && !Array.isArray(payload.props)\n                ? (payload.props as Record<string, unknown>)\n                : {};\n            onEvent({\n              type: \"artifact_update\",\n              id: String(payload.id),\n              props,\n              component: typeof payload.component === \"string\" ? payload.component : undefined\n            });\n            // Accumulate for hydration: embedded on artifact_complete so an\n            // inline component block re-renders with its real props after a\n            // refresh.\n            const updateAcc = artifactContent.get(String(payload.id));\n            if (updateAcc) {\n              updateAcc.props = { ...(updateAcc.props ?? {}), ...props };\n            }\n          } else if (payloadType === \"artifact_complete\") {\n            const artCompleteId = String(payload.id);\n            onEvent({ type: \"artifact_complete\", id: artCompleteId });\n            // Update the inline card to show completed state\n            const refMsg = artifactCardMessages.get(artCompleteId);\n            if (refMsg) {\n              refMsg.streaming = false;\n              try {\n                const parsed = JSON.parse(refMsg.rawContent ?? \"{}\");\n                if (parsed.props) {\n                  parsed.props.status = \"complete\";\n                  // Store markdown content in card props so download works after page refresh\n                  const acc = artifactContent.get(artCompleteId);\n                  if (acc?.markdown) {\n                    parsed.props.markdown = acc.markdown;\n                  }\n                  // Persist file metadata too so the download path unfences correctly after refresh.\n                  if (acc?.file) {\n                    parsed.props.file = acc.file;\n                  }\n                  // Embed accumulated component props so an inline component\n                  // block hydrates with its real props after a refresh. Only\n                  // the inline block reads them; the card never does.\n                  if (\n                    parsed.component === \"PersonaArtifactInline\" &&\n                    acc?.props &&\n                    Object.keys(acc.props).length > 0\n                  ) {\n                    parsed.props.componentProps = acc.props;\n                  }\n                }\n                refMsg.rawContent = JSON.stringify(parsed);\n              } catch { /* ignore parse errors */ }\n              artifactContent.delete(artCompleteId);\n              emitMessage(refMsg);\n              artifactCardMessages.delete(artCompleteId);\n            }\n          }\n        } else if (payloadType === \"transcript_insert\") {\n          const m = payload.message as Record<string, unknown> | undefined;\n          if (!m || typeof m !== \"object\") {\n            continue;\n          }\n          const id = String(m.id ?? `msg-${nextSequence()}`);\n          const roleRaw = m.role;\n          const role =\n            roleRaw === \"user\" ? \"user\" : roleRaw === \"system\" ? \"system\" : \"assistant\";\n          const msg: AgentWidgetMessage = {\n            id,\n            role,\n            content: typeof m.content === \"string\" ? m.content : \"\",\n            rawContent: typeof m.rawContent === \"string\" ? m.rawContent : undefined,\n            createdAt:\n              typeof m.createdAt === \"string\" ? m.createdAt : new Date().toISOString(),\n            streaming: m.streaming === true,\n            // Omit variant unless the stream specifies it. Do not default to `\"assistant\"`:\n            // that value is truthy and skips the component-directive branch (`!message.variant` in ui.ts).\n            ...(typeof m.variant === \"string\"\n              ? { variant: m.variant as AgentWidgetMessage[\"variant\"] }\n              : {}),\n            sequence: nextSequence()\n          };\n          emitMessage(msg);\n          // Detect artifact references in transcript_insert to prevent duplicate auto-cards\n          if (msg.rawContent) {\n            try {\n              const parsed = JSON.parse(msg.rawContent);\n              const refArtId = parsed?.props?.artifactId;\n              if (typeof refArtId === \"string\") {\n                artifactIdsWithCards.add(refArtId);\n              }\n            } catch { /* not JSON or no artifactId */ }\n          }\n          assistantMessage = null;\n          assistantMessageRef.current = null;\n          streamParsers.delete(id);\n          rawContentBuffers.delete(id);\n        } else if (payloadType === \"error\") {\n          // Unified non-terminal error (merged spec). A bare `error` is\n          // recoverable by default — a transient notice such as \"rate limited,\n          // retrying\" — and the execution continues, so it must NOT surface as a\n          // fatal error or finalize the stream. The API routes terminal failures\n          // through `execution_error`. Only an explicit `recoverable: false`\n          // promotes an `error` to terminal.\n          if (\n            payload.recoverable === false &&\n            payload.error != null &&\n            payload.error !== \"\"\n          ) {\n            const errorMessage =\n              typeof payload.error === \"string\"\n                ? payload.error\n                : (payload.error as { message?: unknown })?.message != null\n                  ? String((payload.error as { message?: unknown }).message)\n                  : \"Execution error\";\n            onEvent({ type: \"error\", error: new Error(errorMessage) });\n            const finalMsg = assistantMessage as AgentWidgetMessage | null;\n            if (finalMsg && finalMsg.streaming) {\n              finalMsg.streaming = false;\n              emitMessage(finalMsg);\n            }\n            onEvent({ type: \"status\", status: \"idle\" });\n          }\n        } else if (\n          payloadType === \"step_error\" ||\n          payloadType === \"dispatch_error\" ||\n          payloadType === \"flow_error\"\n        ) {\n          let resolvedError: Error | null = null;\n          if (payload.error instanceof Error) {\n            resolvedError = payload.error;\n          } else if (payloadType === \"dispatch_error\") {\n            const msg = payload.message ?? payload.error;\n            if (msg != null && msg !== \"\") {\n              resolvedError = new Error(String(msg));\n            }\n          } else {\n            const e = payload.error;\n            if (typeof e === \"string\" && e !== \"\") {\n              resolvedError = new Error(e);\n            } else if (e != null && typeof e === \"object\" && Reflect.has(e, \"message\")) {\n              // Reflect.has, not `in` — see the note on the equivalent guard above.\n              resolvedError = new Error(String((e as { message?: unknown }).message ?? e));\n            }\n          }\n\n          if (resolvedError) {\n            onEvent({ type: \"error\", error: resolvedError });\n            const finalMsg = assistantMessage as AgentWidgetMessage | null;\n            if (finalMsg && finalMsg.streaming) {\n              finalMsg.streaming = false;\n              emitMessage(finalMsg);\n            }\n            onEvent({ type: \"status\", status: \"idle\" });\n          }\n        }\n      }\n      seqReadyQueue.length = 0;\n    };\n\n    // eslint-disable-next-line no-constant-condition\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n\n      buffer += decoder.decode(value, { stream: true });\n      const events = buffer.split(\"\\n\\n\");\n      buffer = events.pop() ?? \"\";\n\n      for (const event of events) {\n        const lines = event.split(\"\\n\");\n        let eventType = \"message\";\n        let data = \"\";\n        // Durable-reconnect cursor: the SSE `id:` line (the durable row seq).\n        // Only durable, resumable agent executions stamp these (e.g. Claude\n        // Managed agents, or any async/background run the backend persists and\n        // can replay); other streams carry no cursor. We emit a `cursor`\n        // event AFTER the frame is fully parsed and dispatched, so the session's\n        // `lastEventId` only advances past frames it has actually applied, so the\n        // happy path has no dupes against the server's `seq > after` replay.\n        let frameId: string | null = null;\n\n        for (const line of lines) {\n          if (line.startsWith(\"event:\")) {\n            eventType = line.replace(\"event:\", \"\").trim();\n          } else if (line.startsWith(\"data:\")) {\n            data += line.replace(\"data:\", \"\").trim();\n          } else if (line.startsWith(\"id:\")) {\n            frameId = line.slice(3).trim();\n          }\n        }\n\n        const advanceCursor = () => {\n          if (frameId !== null && frameId !== \"\") {\n            onEvent({ type: \"cursor\", id: frameId });\n          }\n        };\n\n        // A frame with an `id:` but no `data:` (e.g. a bare keepalive line) is\n        // still a received durable row, so advance the cursor past it.\n        if (!data) {\n          advanceCursor();\n          continue;\n        }\n        let payload: any;\n        try {\n          payload = JSON.parse(data);\n        } catch (error) {\n          // Parse failure: the frame was NOT applied. Do NOT advance the cursor\n          // so a reconnect re-fetches this row.\n          onEvent({\n            type: \"error\",\n            error:\n              error instanceof Error\n                ? error\n                : new Error(\"Failed to parse chat stream payload\")\n          });\n          continue;\n        }\n\n        const payloadType =\n          eventType !== \"message\" ? eventType : payload.type ?? \"message\";\n\n        // Tap: capture raw SSE event for event stream inspector\n        this.onSSEEvent?.(payloadType, payload);\n\n        // If custom SSE event parser is provided, try it first\n        if (this.parseSSEEvent) {\n          // Keep assistant message ref in sync\n          assistantMessageRef.current = assistantMessage;\n          const handled = await this.handleCustomSSEEvent(\n            payload,\n            onEvent,\n            assistantMessageRef,\n            emitMessage,\n            nextSequence,\n            customParsePartId\n          );\n          // Update assistantMessage from ref (in case it was created or replaced by partId segmentation)\n          if (assistantMessageRef.current && assistantMessageRef.current !== assistantMessage) {\n            assistantMessage = assistantMessageRef.current;\n          }\n          if (handled) {\n            advanceCursor();\n            continue; // Skip default handling if custom handler processed it\n          }\n        }\n\n        // The wire is the wire vocabulary; the handler consumes it\n        // natively. The stream is single-connection and in order, so each frame\n        // drains straight through.\n        seqReadyQueue.push({ payloadType, payload });\n        drainReadyQueue();\n        advanceCursor();\n      }\n    }\n\n    drainReadyQueue();\n  }\n}\n","/**\n * Message ID utilities for client-side message tracking\n * Used for feedback integration with the Runtype API\n */\n\n/**\n * Generate a unique message ID for tracking\n * Format: msg_{timestamp_base36}_{random_8chars}\n */\nexport function generateMessageId(): string {\n  const timestamp = Date.now().toString(36);\n  const random = Math.random().toString(36).substring(2, 10);\n  return `msg_${timestamp}_${random}`;\n}\n\n/**\n * Generate a unique user message ID\n * Format: usr_{timestamp_base36}_{random_8chars}\n */\nexport function generateUserMessageId(): string {\n  const timestamp = Date.now().toString(36);\n  const random = Math.random().toString(36).substring(2, 10);\n  return `usr_${timestamp}_${random}`;\n}\n\n/**\n * Generate a unique assistant message ID\n * Format: ast_{timestamp_base36}_{random_8chars}\n */\nexport function generateAssistantMessageId(): string {\n  const timestamp = Date.now().toString(36);\n  const random = Math.random().toString(36).substring(2, 10);\n  return `ast_${timestamp}_${random}`;\n}\n\n\n\n","/**\n * Content Utilities\n *\n * Helper functions for working with multi-modal message content.\n */\n\nimport type { MessageContent, ContentPart, TextContentPart, ImageContentPart, FileContentPart } from '../types';\n\n/**\n * Fallback display text for messages that only contain image attachments.\n */\nexport const IMAGE_ONLY_MESSAGE_FALLBACK_TEXT = \"[Image]\";\n\n/**\n * Normalize content to ContentPart[] format.\n * Converts string content to a single text content part.\n */\nexport function normalizeContent(content: MessageContent): ContentPart[] {\n  if (typeof content === 'string') {\n    return [{ type: 'text', text: content }];\n  }\n  return content;\n}\n\n/**\n * Extract display text from content parts.\n * Concatenates all text parts into a single string.\n */\nexport function getDisplayText(content: MessageContent): string {\n  if (typeof content === 'string') {\n    return content;\n  }\n  return content\n    .filter((part): part is TextContentPart => part.type === 'text')\n    .map(part => part.text)\n    .join('');\n}\n\n/**\n * Check if content contains any images.\n */\nexport function hasImages(content: MessageContent): boolean {\n  if (typeof content === 'string') {\n    return false;\n  }\n  return content.some(part => part.type === 'image');\n}\n\n/**\n * Get all image parts from content.\n */\nexport function getImageParts(content: MessageContent): ImageContentPart[] {\n  if (typeof content === 'string') {\n    return [];\n  }\n  return content.filter((part): part is ImageContentPart => part.type === 'image');\n}\n\n/**\n * Create a text-only content part.\n */\nexport function createTextPart(text: string): TextContentPart {\n  return { type: 'text', text };\n}\n\n/**\n * Create an image content part from a base64 data URI or URL.\n *\n * @param image - Base64 data URI (data:image/...) or URL\n * @param options - Optional mimeType and alt text\n */\nexport function createImagePart(\n  image: string,\n  options?: { mimeType?: string; alt?: string }\n): ImageContentPart {\n  return {\n    type: 'image',\n    image,\n    ...(options?.mimeType && { mimeType: options.mimeType }),\n    ...(options?.alt && { alt: options.alt }),\n  };\n}\n\n/**\n * Convert a File object to an image content part.\n * Reads the file and converts it to a base64 data URI.\n */\nexport async function fileToImagePart(file: File): Promise<ImageContentPart> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = () => {\n      const dataUri = reader.result as string;\n      resolve({\n        type: 'image',\n        image: dataUri,\n        mimeType: file.type,\n        alt: file.name,\n      });\n    };\n    reader.onerror = () => reject(new Error('Failed to read file'));\n    reader.readAsDataURL(file);\n  });\n}\n\n/**\n * Validate that a file is an acceptable image type.\n *\n * @param file - The file to validate\n * @param acceptedTypes - Array of accepted MIME types (default: common image types)\n * @param maxSizeBytes - Maximum file size in bytes (default: 10MB)\n */\nexport function validateImageFile(\n  file: File,\n  acceptedTypes: string[] = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],\n  maxSizeBytes: number = 10 * 1024 * 1024\n): { valid: boolean; error?: string } {\n  if (!acceptedTypes.includes(file.type)) {\n    return {\n      valid: false,\n      error: `Invalid file type. Accepted types: ${acceptedTypes.join(', ')}`,\n    };\n  }\n\n  if (file.size > maxSizeBytes) {\n    const maxSizeMB = Math.round(maxSizeBytes / (1024 * 1024));\n    return {\n      valid: false,\n      error: `File too large. Maximum size: ${maxSizeMB}MB`,\n    };\n  }\n\n  return { valid: true };\n}\n\n// ============================================================================\n// Generic File Utilities (for PDF, TXT, DOCX, etc.)\n// ============================================================================\n\n/**\n * Common image MIME types\n */\nexport const IMAGE_MIME_TYPES = [\n  'image/png',\n  'image/jpeg',\n  'image/gif',\n  'image/webp',\n  'image/svg+xml',\n  'image/bmp',\n];\n\n/**\n * Common document MIME types\n */\nexport const DOCUMENT_MIME_TYPES = [\n  'application/pdf',\n  'text/plain',\n  'text/markdown',\n  'text/csv',\n  'application/msword',\n  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n  'application/vnd.ms-excel',\n  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n  'application/json',\n];\n\n/**\n * All supported file types (images + documents)\n */\nexport const ALL_SUPPORTED_MIME_TYPES = [...IMAGE_MIME_TYPES, ...DOCUMENT_MIME_TYPES];\n\n/**\n * Check if a MIME type is an image\n */\nexport function isImageMimeType(mimeType: string): boolean {\n  return IMAGE_MIME_TYPES.includes(mimeType) || mimeType.startsWith('image/');\n}\n\n/**\n * Check if a file is an image\n */\nexport function isImageFile(file: File): boolean {\n  return isImageMimeType(file.type);\n}\n\n/**\n * Create a file content part from a base64 data URI.\n */\nexport function createFilePart(\n  data: string,\n  mimeType: string,\n  filename: string\n): FileContentPart {\n  return {\n    type: 'file',\n    data,\n    mimeType,\n    filename,\n  };\n}\n\n/**\n * Convert a File object to a content part.\n * Returns ImageContentPart for images, FileContentPart for other files.\n */\nexport async function fileToContentPart(file: File): Promise<ImageContentPart | FileContentPart> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = () => {\n      const dataUri = reader.result as string;\n\n      if (isImageFile(file)) {\n        // Return image content part for images\n        resolve({\n          type: 'image',\n          image: dataUri,\n          mimeType: file.type,\n          alt: file.name,\n        });\n      } else {\n        // Return file content part for documents\n        resolve({\n          type: 'file',\n          data: dataUri,\n          mimeType: file.type,\n          filename: file.name,\n        });\n      }\n    };\n    reader.onerror = () => reject(new Error('Failed to read file'));\n    reader.readAsDataURL(file);\n  });\n}\n\n/**\n * Validate that a file is an acceptable type.\n *\n * @param file - The file to validate\n * @param acceptedTypes - Array of accepted MIME types\n * @param maxSizeBytes - Maximum file size in bytes (default: 10MB)\n */\nexport function validateFile(\n  file: File,\n  acceptedTypes: string[] = ALL_SUPPORTED_MIME_TYPES,\n  maxSizeBytes: number = 10 * 1024 * 1024\n): { valid: boolean; error?: string } {\n  if (!acceptedTypes.includes(file.type)) {\n    return {\n      valid: false,\n      error: `Invalid file type \"${file.type}\". Accepted types: ${acceptedTypes.join(', ')}`,\n    };\n  }\n\n  if (file.size > maxSizeBytes) {\n    const maxSizeMB = Math.round(maxSizeBytes / (1024 * 1024));\n    return {\n      valid: false,\n      error: `File too large. Maximum size: ${maxSizeMB}MB`,\n    };\n  }\n\n  return { valid: true };\n}\n\n/**\n * Get file parts from content.\n */\nexport function getFileParts(content: MessageContent): FileContentPart[] {\n  if (typeof content === 'string') {\n    return [];\n  }\n  return content.filter((part): part is FileContentPart => part.type === 'file');\n}\n\n/**\n * Check if content contains any files.\n */\nexport function hasFiles(content: MessageContent): boolean {\n  if (typeof content === 'string') {\n    return false;\n  }\n  return content.some(part => part.type === 'file');\n}\n\n/**\n * Get file extension from filename\n */\nexport function getFileExtension(filename: string): string {\n  const parts = filename.split('.');\n  return parts.length > 1 ? parts.pop()!.toLowerCase() : '';\n}\n\n/**\n * Get a display-friendly file type name\n */\nexport function getFileTypeName(mimeType: string, filename: string): string {\n  const ext = getFileExtension(filename).toUpperCase();\n\n  const typeMap: Record<string, string> = {\n    'application/pdf': 'PDF',\n    'text/plain': 'TXT',\n    'text/markdown': 'MD',\n    'text/csv': 'CSV',\n    'application/msword': 'DOC',\n    'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX',\n    'application/vnd.ms-excel': 'XLS',\n    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',\n    'application/json': 'JSON',\n  };\n\n  return typeMap[mimeType] || ext || 'FILE';\n}\n","// Runtype Voice Provider\n//\n// Real-time streaming voice client for Runtype's `/ws/agents/:agentId/voice`\n// endpoint. The \"call\" is a single WebSocket session:\n//\n//   - up:   continuous mic audio as raw PCM16 LE mono @ 16kHz (binary frames)\n//   - down: WAV-wrapped PCM16 LE mono @ 24kHz audio (binary frames) +\n//           JSON control frames (transcript_interim / transcript_final /\n//           audio_end / metrics).\n//\n// The server's STT owns turn-taking, so the client streams continuously and\n// has no client-side VAD, barge-in monitoring, or batch upload. Auth rides the\n// `Sec-WebSocket-Protocol` subprotocol (`['runtype.bearer', clientToken]`),\n// never the query string: the token is never placed in a URL or logged.\n//\n// A continuous always-hot mic is, in UX terms, a permanent barge-in session, so\n// `getInterruptionMode()` reports the constant `'barge-in'` and the existing\n// mic-button wiring (ui.ts) treats a click as \"hang up at any state\" unchanged.\n\nimport type {\n  VoiceProvider,\n  VoiceResult,\n  VoiceStatus,\n  VoiceConfig,\n  VoiceMetrics,\n  VoicePlaybackEngine,\n} from \"../types\";\nimport { AudioPlaybackManager } from \"./audio-playback-manager\";\n\nconst CAPTURE_SAMPLE_RATE = 16000;\nconst PLAYBACK_SAMPLE_RATE = 24000;\nconst CAPTURE_BUFFER_SIZE = 4096;\nconst RIFF_MAGIC = 0x52494646; // \"RIFF\"\n\n/**\n * Strip the canonical 44-byte WAV header (if present) and return the raw PCM16\n * payload. The ElevenLabs realtime path WAV-wraps each frame; the Cloudflare DO\n * path may send raw PCM: detect the RIFF magic and handle both.\n */\nfunction stripWavHeader(buf: ArrayBuffer): Uint8Array {\n  if (buf.byteLength >= 44) {\n    const view = new DataView(buf);\n    if (view.getUint32(0, false) === RIFF_MAGIC) {\n      return new Uint8Array(buf, 44);\n    }\n  }\n  return new Uint8Array(buf);\n}\n\n/** Derive a ws(s):// base URL from a configured host (full URL or bare host). */\nfunction toWsBase(host: string): string {\n  const trimmed = host.replace(/\\/+$/, \"\");\n  if (/^wss?:\\/\\//i.test(trimmed)) return trimmed;\n  if (/^https?:\\/\\//i.test(trimmed)) return trimmed.replace(/^http/i, \"ws\");\n  const secure =\n    typeof window !== \"undefined\" && window.location?.protocol === \"https:\";\n  return `${secure ? \"wss:\" : \"ws:\"}//${trimmed}`;\n}\n\nexport class RuntypeVoiceProvider implements VoiceProvider {\n  type: \"runtype\" = \"runtype\";\n\n  private ws: WebSocket | null = null;\n  private captureContext: AudioContext | null = null;\n  private mediaStream: MediaStream | null = null;\n  private sourceNode: MediaStreamAudioSourceNode | null = null;\n  private processor: ScriptProcessorNode | null = null;\n  private playback: VoicePlaybackEngine | null = null;\n\n  // True while a call (WS session) is live: drives the idempotent start guard\n  // and `isBargeInActive()`.\n  private callLive = false;\n  private isSpeaking = false;\n\n  // Invalidates in-flight async work (playback-engine creation, late frames,\n  // status transitions) after a teardown/restart so a stale callback can't act\n  // on a newer call's resources. Bumped on every start and every cleanup.\n  private callGeneration = 0;\n\n  // Distinguishes a user-initiated close (code 1000) from a dropped connection.\n  private intentionalClose = false;\n\n  private resultCallbacks: ((result: VoiceResult) => void)[] = [];\n  private errorCallbacks: ((error: Error) => void)[] = [];\n  private statusCallbacks: ((status: VoiceStatus) => void)[] = [];\n  private transcriptCallbacks: ((\n    role: \"user\" | \"assistant\",\n    text: string,\n    isFinal: boolean,\n  ) => void)[] = [];\n  private metricsCallbacks: ((metrics: VoiceMetrics) => void)[] = [];\n\n  constructor(private config: VoiceConfig[\"runtype\"]) {}\n\n  // --- VoiceProvider lifecycle ----------------------------------------------\n\n  /** No-op: the WS session opens lazily in `startListening` (the \"call\"). */\n  async connect(): Promise<void> {}\n\n  /** Start the call: acquire mic, open the WS, stream PCM until hang-up. */\n  async startListening(): Promise<void> {\n    if (this.callLive) return; // idempotent: a call is already live\n\n    const agentId = this.config?.agentId;\n    const token = this.config?.clientToken;\n    const host = this.config?.host;\n    if (!agentId) throw new Error(\"Runtype voice requires an agentId\");\n    if (!token) throw new Error(\"Runtype voice requires a clientToken\");\n    if (!host) throw new Error(\"Runtype voice requires a host (or widget apiUrl)\");\n\n    const generation = ++this.callGeneration;\n    this.intentionalClose = false;\n    this.callLive = true;\n\n    try {\n      const stream = await navigator.mediaDevices.getUserMedia({\n        audio: {\n          sampleRate: CAPTURE_SAMPLE_RATE,\n          channelCount: 1,\n          echoCancellation: true,\n        },\n      });\n      if (generation !== this.callGeneration) {\n        stream.getTracks().forEach((t) => t.stop());\n        return;\n      }\n      this.mediaStream = stream;\n\n      // Create + resume both contexts inside the click gesture (iOS autoplay).\n      const AudioCtx =\n        (window as any).AudioContext || (window as any).webkitAudioContext;\n      const captureContext: AudioContext = new AudioCtx({\n        sampleRate: CAPTURE_SAMPLE_RATE,\n      });\n      if (captureContext.state === \"suspended\") {\n        await captureContext.resume().catch(() => {});\n      }\n      this.captureContext = captureContext;\n\n      const engine = this.config?.createPlaybackEngine\n        ? await this.config.createPlaybackEngine()\n        : new AudioPlaybackManager(PLAYBACK_SAMPLE_RATE);\n      if (generation !== this.callGeneration) {\n        // Torn down while async work was in flight: free what we acquired.\n        void engine.destroy();\n        stream.getTracks().forEach((t) => t.stop());\n        captureContext.close().catch(() => {});\n        return;\n      }\n      this.playback = engine;\n      engine.onFinished(() => {\n        if (generation !== this.callGeneration) return;\n        this.isSpeaking = false;\n        // Reply drained: the call stays open, so return to listening.\n        if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n          this.emitStatus(\"listening\");\n        }\n      });\n\n      const wsUrl = `${toWsBase(host)}/ws/agents/${encodeURIComponent(agentId)}/voice`;\n      // Token rides the subprotocol; `runtype.bearer` is the marker the server\n      // echoes as the negotiated subprotocol (browsers fail the handshake if an\n      // offered subprotocol goes unanswered).\n      const ws = new WebSocket(wsUrl, [\"runtype.bearer\", token]);\n      ws.binaryType = \"arraybuffer\";\n      this.ws = ws;\n\n      ws.onopen = () => {\n        if (generation !== this.callGeneration) return;\n        this.emitStatus(\"listening\");\n        this.startCapture(captureContext, stream, ws, generation);\n      };\n\n      ws.onmessage = (event) => this.handleMessage(event, generation);\n\n      ws.onerror = () => {\n        if (generation !== this.callGeneration) return;\n        this.emitError(new Error(\"Voice connection failed\"));\n        this.emitStatus(\"error\");\n        this.cleanup();\n      };\n\n      ws.onclose = (evt) => {\n        if (this.intentionalClose) {\n          this.intentionalClose = false;\n          return;\n        }\n        if (generation !== this.callGeneration) return;\n        if (evt.code !== 1000) {\n          const codeMsg = evt.code ? ` (code ${evt.code})` : \"\";\n          this.emitError(new Error(`Voice connection closed${codeMsg}`));\n          this.emitStatus(\"error\");\n        } else {\n          this.emitStatus(\"idle\");\n        }\n        this.cleanup();\n      };\n    } catch (error) {\n      this.cleanup();\n      this.emitError(error as Error);\n      this.emitStatus(\"error\");\n      throw error;\n    }\n  }\n\n  /** End the call (hang up). */\n  async stopListening(): Promise<void> {\n    this.cleanup();\n    this.emitStatus(\"idle\");\n  }\n\n  /** Tear down the call and drop all callbacks (used by `cleanupVoice`). */\n  async disconnect(): Promise<void> {\n    this.cleanup();\n    this.emitStatus(\"disconnected\");\n    this.resultCallbacks = [];\n    this.errorCallbacks = [];\n    this.statusCallbacks = [];\n    this.transcriptCallbacks = [];\n    this.metricsCallbacks = [];\n  }\n\n  /** Stop the spoken reply without ending the call. */\n  stopPlayback(): void {\n    if (this.playback) this.playback.flush();\n    this.isSpeaking = false;\n    if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n      this.emitStatus(\"listening\");\n    }\n  }\n\n  // --- Barge-in surface (constants for the continuous hot-mic model) --------\n\n  /** A continuous call is a permanent barge-in session. */\n  getInterruptionMode(): \"none\" | \"cancel\" | \"barge-in\" {\n    return \"barge-in\";\n  }\n\n  /** True while the call (hot mic) is live. */\n  isBargeInActive(): boolean {\n    return this.callLive;\n  }\n\n  /** \"Hang up\" the always-on mic. */\n  async deactivateBargeIn(): Promise<void> {\n    this.cleanup();\n    this.emitStatus(\"idle\");\n  }\n\n  // --- Capture ---------------------------------------------------------------\n\n  private startCapture(\n    context: AudioContext,\n    stream: MediaStream,\n    ws: WebSocket,\n    generation: number,\n  ): void {\n    const source = context.createMediaStreamSource(stream);\n    this.sourceNode = source;\n    const processor = context.createScriptProcessor(CAPTURE_BUFFER_SIZE, 1, 1);\n    this.processor = processor;\n\n    processor.onaudioprocess = (e) => {\n      if (generation !== this.callGeneration) return;\n      if (ws.readyState !== WebSocket.OPEN) return;\n      const input = e.inputBuffer.getChannelData(0);\n      const pcm16 = new Int16Array(input.length);\n      for (let i = 0; i < input.length; i++) {\n        const s = Math.max(-1, Math.min(1, input[i]));\n        pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n      }\n      ws.send(pcm16.buffer);\n    };\n\n    source.connect(processor);\n    // The processor must be connected to the graph to run; it writes no output,\n    // so the destination receives silence (no mic echo).\n    processor.connect(context.destination);\n  }\n\n  // --- Downstream ------------------------------------------------------------\n\n  private handleMessage(event: MessageEvent, generation: number): void {\n    if (generation !== this.callGeneration) return;\n\n    if (event.data instanceof ArrayBuffer) {\n      this.handleAudioFrame(event.data, generation);\n      return;\n    }\n\n    let msg: any;\n    try {\n      msg = JSON.parse(event.data as string);\n    } catch {\n      return; // non-JSON, non-binary frame: ignore\n    }\n\n    switch (msg.type) {\n      case \"transcript_interim\":\n        this.emitStatus(\"listening\");\n        this.emitTranscript(\"user\", msg.text ?? \"\", false);\n        break;\n\n      case \"transcript_final\": {\n        const role = msg.role === \"assistant\" ? \"assistant\" : \"user\";\n        // user final → agent is now thinking; assistant final → reply incoming.\n        this.emitStatus(role === \"user\" ? \"processing\" : \"speaking\");\n        this.emitTranscript(role, msg.text ?? \"\", true);\n        break;\n      }\n\n      case \"audio_end\":\n        if (this.playback) {\n          this.playback.markStreamEnd();\n        } else {\n          this.isSpeaking = false;\n          this.emitStatus(\"listening\");\n        }\n        break;\n\n      case \"metrics\":\n        this.emitMetrics({\n          llmMs: msg.llm_ms,\n          ttsMs: msg.tts_ms,\n          firstAudioMs: msg.first_audio_ms,\n          totalMs: msg.total_ms,\n        });\n        break;\n\n      case \"error\":\n        this.emitError(new Error(msg.error || \"Voice error\"));\n        this.emitStatus(\"error\");\n        break;\n    }\n  }\n\n  private handleAudioFrame(buf: ArrayBuffer, generation: number): void {\n    if (generation !== this.callGeneration) return;\n    if (!this.playback) return;\n    const pcm = stripWavHeader(buf);\n    if (pcm.length === 0) return;\n    if (!this.isSpeaking) {\n      this.isSpeaking = true;\n      this.emitStatus(\"speaking\");\n    }\n    this.playback.enqueue(pcm);\n  }\n\n  // --- Teardown --------------------------------------------------------------\n\n  private cleanup(): void {\n    // Invalidate any in-flight async continuation / late frames first.\n    this.callGeneration += 1;\n    this.callLive = false;\n    this.isSpeaking = false;\n\n    if (this.processor) {\n      this.processor.onaudioprocess = null;\n      this.processor.disconnect();\n      this.processor = null;\n    }\n    if (this.sourceNode) {\n      this.sourceNode.disconnect();\n      this.sourceNode = null;\n    }\n    if (this.mediaStream) {\n      this.mediaStream.getTracks().forEach((t) => t.stop());\n      this.mediaStream = null;\n    }\n    if (this.captureContext) {\n      this.captureContext.close().catch(() => {});\n      this.captureContext = null;\n    }\n    if (this.playback) {\n      void this.playback.destroy();\n      this.playback = null;\n    }\n    if (this.ws) {\n      this.intentionalClose = true;\n      try {\n        this.ws.close(1000, \"client ended call\");\n      } catch {\n        // ignore\n      }\n      this.ws = null;\n    }\n  }\n\n  // --- Callback registration + emit -----------------------------------------\n\n  onResult(callback: (result: VoiceResult) => void): void {\n    this.resultCallbacks.push(callback);\n  }\n\n  onError(callback: (error: Error) => void): void {\n    this.errorCallbacks.push(callback);\n  }\n\n  onStatusChange(callback: (status: VoiceStatus) => void): void {\n    this.statusCallbacks.push(callback);\n  }\n\n  onTranscript(\n    callback: (role: \"user\" | \"assistant\", text: string, isFinal: boolean) => void,\n  ): void {\n    this.transcriptCallbacks.push(callback);\n  }\n\n  onMetrics(callback: (metrics: VoiceMetrics) => void): void {\n    this.metricsCallbacks.push(callback);\n  }\n\n  private emitStatus(status: VoiceStatus): void {\n    this.statusCallbacks.forEach((cb) => cb(status));\n  }\n\n  private emitError(error: Error): void {\n    this.errorCallbacks.forEach((cb) => cb(error));\n  }\n\n  private emitTranscript(\n    role: \"user\" | \"assistant\",\n    text: string,\n    isFinal: boolean,\n  ): void {\n    this.transcriptCallbacks.forEach((cb) => cb(role, text, isFinal));\n  }\n\n  private emitMetrics(metrics: VoiceMetrics): void {\n    this.metricsCallbacks.forEach((cb) => cb(metrics));\n  }\n}\n","// Browser Voice Provider\n// Fallback implementation using Web Speech API\n\nimport type { VoiceProvider, VoiceResult, VoiceStatus, VoiceConfig } from '../types';\n\nexport class BrowserVoiceProvider implements VoiceProvider {\n  type: 'browser' = 'browser';\n  private recognition: any = null;\n  private resultCallbacks: ((result: VoiceResult) => void)[] = [];\n  private errorCallbacks: ((error: Error) => void)[] = [];\n  private statusCallbacks: ((status: VoiceStatus) => void)[] = [];\n  private isListening = false;\n  private w: any = typeof window !== 'undefined' ? window : undefined;\n\n  constructor(private config: VoiceConfig['browser'] = {}) {}\n\n  async connect() {\n    // Browser provider doesn't need connection\n    this.statusCallbacks.forEach(cb => cb('connected'));\n  }\n\n  async startListening() {\n    try {\n      if (this.isListening) {\n        throw new Error('Already listening');\n      }\n      \n      if (!this.w) {\n        throw new Error('Window object not available');\n      }\n      \n      // @ts-ignore - Browser SpeechRecognition API\n      const SpeechRecognition = this.w!.SpeechRecognition || this.w!.webkitSpeechRecognition;\n      \n      if (!SpeechRecognition) {\n        throw new Error('Browser speech recognition not supported');\n      }\n      \n      this.recognition = new SpeechRecognition();\n      this.recognition.lang = this.config?.language || 'en-US';\n      this.recognition.continuous = this.config?.continuous || false;\n      this.recognition.interimResults = true;\n      \n      this.recognition.onresult = (event: any) => {\n        const transcript = Array.from(event.results)\n          .map((result: any) => result[0])\n          .map((result: any) => result.transcript)\n          .join('');\n        \n        const isFinal = event.results[event.results.length - 1].isFinal;\n        \n        this.resultCallbacks.forEach(cb => cb({\n          text: transcript,\n          confidence: isFinal ? 0.8 : 0.5,\n          provider: 'browser'\n        }));\n        \n        if (isFinal && !this.config?.continuous) {\n          this.stopListening();\n        }\n      };\n      \n      this.recognition.onerror = (event: any) => {\n        this.errorCallbacks.forEach(cb => cb(new Error(event.error)));\n        this.statusCallbacks.forEach(cb => cb('error'));\n      };\n      \n      this.recognition.onstart = () => {\n        this.isListening = true;\n        this.statusCallbacks.forEach(cb => cb('listening'));\n      };\n      \n      this.recognition.onend = () => {\n        this.isListening = false;\n        this.statusCallbacks.forEach(cb => cb('idle'));\n      };\n      \n      this.recognition.start();\n      \n    } catch (error) {\n      this.errorCallbacks.forEach(cb => cb(error as Error));\n      this.statusCallbacks.forEach(cb => cb('error'));\n      throw error;\n    }\n  }\n\n  async stopListening() {\n    if (this.recognition) {\n      this.recognition.stop();\n      this.recognition = null;\n    }\n    \n    this.isListening = false;\n    this.statusCallbacks.forEach(cb => cb('idle'));\n  }\n\n  onResult(callback: (result: VoiceResult) => void): void {\n    this.resultCallbacks.push(callback);\n  }\n\n  onError(callback: (error: Error) => void): void {\n    this.errorCallbacks.push(callback);\n  }\n\n  onStatusChange(callback: (status: VoiceStatus) => void): void {\n    this.statusCallbacks.push(callback);\n  }\n\n  async disconnect(): Promise<void> {\n    await this.stopListening();\n    this.statusCallbacks.forEach(cb => cb('disconnected'));\n  }\n\n  // Check if browser supports speech recognition\n  static isSupported(): boolean {\n    // @ts-ignore\n    return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;\n  }\n}","// Voice Provider Factory\n// Creates appropriate voice provider based on configuration\n\nimport type { VoiceProvider, VoiceConfig } from '../types';\nimport { RuntypeVoiceProvider } from './runtype-voice-provider';\nimport { BrowserVoiceProvider } from './browser-voice-provider';\n\nexport function createVoiceProvider(config: VoiceConfig): VoiceProvider {\n  switch (config.type) {\n    case 'runtype':\n      if (!config.runtype) {\n        throw new Error('Runtype voice provider requires configuration');\n      }\n      return new RuntypeVoiceProvider(config.runtype);\n    \n    case 'browser':\n      if (!BrowserVoiceProvider.isSupported()) {\n        throw new Error('Browser speech recognition not supported');\n      }\n      return new BrowserVoiceProvider(config.browser || {});\n    \n    case 'custom': {\n      // Bring-your-own provider: `custom` is either a ready VoiceProvider\n      // instance or a `() => VoiceProvider` factory (deferred construction, so\n      // resources like a WebSocket or AudioContext are only created when voice\n      // is actually set up). Resolve and sanity-check the shape.\n      const custom = config.custom;\n      if (!custom) {\n        throw new Error(\n          'Custom voice provider requires a `custom` provider instance or factory'\n        );\n      }\n      const provider = typeof custom === 'function' ? custom() : custom;\n      if (!provider || typeof provider.startListening !== 'function') {\n        throw new Error(\n          'Custom voice provider `custom` must be a VoiceProvider (or a factory returning one)'\n        );\n      }\n      return provider;\n    }\n\n    default:\n      throw new Error(`Unknown voice provider type: ${config.type}`);\n  }\n}\n\n// Auto-select the best available provider\nexport function createBestAvailableVoiceProvider(config?: Partial<VoiceConfig>): VoiceProvider {\n  // Honor an explicit bring-your-own provider before any built-in.\n  if (config?.type === 'custom' && config.custom) {\n    return createVoiceProvider({ type: 'custom', custom: config.custom });\n  }\n\n  // Prefer Runtype if configured\n  if (config?.type === 'runtype' && config.runtype) {\n    return createVoiceProvider({ type: 'runtype', runtype: config.runtype });\n  }\n\n  // Fall back to browser if supported\n  if (BrowserVoiceProvider.isSupported()) {\n    return createVoiceProvider({ \n      type: 'browser', \n      browser: config?.browser || { language: 'en-US' }\n    });\n  }\n  \n  throw new Error('No supported voice providers available');\n}\n\n// Check if any voice provider is available\nexport function isVoiceSupported(config?: Partial<VoiceConfig>): boolean {\n  try {\n    createBestAvailableVoiceProvider(config);\n    return true;\n  } catch (error) {\n    return false;\n  }\n}","// Browser Speech Engine\n//\n// Default `SpeechEngine` for the per-message \"Read aloud\" action and the\n// auto-speak path, backed by the browser Web Speech API\n// (`window.speechSynthesis`). Zero-backend and offline-capable, but limited to\n// the OS/browser voice set.\n//\n// A hosted engine (Runtype TTS, ElevenLabs, a server proxy, …) implements the\n// same `SpeechEngine` interface and is supplied via\n// `textToSpeech.createEngine`; such an engine can stream PCM into the realtime\n// voice `VoicePlaybackEngine` (see `audio-playback-manager.ts`). Nothing in the\n// `ReadAloudController` is browser-specific — only this file is.\n\nimport type { SpeechCallbacks, SpeechEngine, SpeechRequest } from \"../types\";\n\n/**\n * Pick the best available English voice from the browser's voice list.\n * Prefers high-quality remote/natural voices, then enhanced local voices,\n * then standard local voices, then any English voice, then the first voice.\n */\nexport function pickBestVoice(voices: SpeechSynthesisVoice[]): SpeechSynthesisVoice {\n  // Priority list: high-quality voices across browsers/platforms.\n  const preferred = [\n    // Edge Online Natural (highest quality)\n    \"Microsoft Jenny Online (Natural) - English (United States)\",\n    \"Microsoft Aria Online (Natural) - English (United States)\",\n    \"Microsoft Guy Online (Natural) - English (United States)\",\n    // Google remote (good quality, cross-platform in Chrome)\n    \"Google US English\",\n    \"Google UK English Female\",\n    // Apple premium/enhanced (macOS)\n    \"Ava (Premium)\",\n    \"Evan (Enhanced)\",\n    \"Samantha (Enhanced)\",\n    // Apple standard (macOS/iOS)\n    \"Samantha\",\n    \"Daniel\",\n    \"Karen\",\n    // Windows SAPI\n    \"Microsoft David Desktop - English (United States)\",\n    \"Microsoft Zira Desktop - English (United States)\",\n  ];\n\n  for (const name of preferred) {\n    const match = voices.find((v) => v.name === name);\n    if (match) return match;\n  }\n\n  // Fallback: any English voice, then first available.\n  return voices.find((v) => v.lang.startsWith(\"en\")) ?? voices[0];\n}\n\nexport interface BrowserSpeechEngineOptions {\n  /** Custom voice picker, used when no exact `voice` name is requested. */\n  pickVoice?: (voices: SpeechSynthesisVoice[]) => SpeechSynthesisVoice;\n}\n\n/** Default `SpeechEngine` backed by the browser Web Speech API. */\nexport class BrowserSpeechEngine implements SpeechEngine {\n  readonly id = \"browser\";\n  // speechSynthesis exposes pause()/resume(); reliable for start/stop and\n  // serviceable for pause on most engines (Chrome has known quirks on resume).\n  readonly supportsPause = true;\n\n  constructor(private options: BrowserSpeechEngineOptions = {}) {}\n\n  /** Whether the Web Speech API is available in this environment. */\n  static isSupported(): boolean {\n    return typeof window !== \"undefined\" && \"speechSynthesis\" in window;\n  }\n\n  speak(request: SpeechRequest, callbacks: SpeechCallbacks): void {\n    if (!BrowserSpeechEngine.isSupported()) {\n      callbacks.onError?.(new Error(\"Web Speech API is unavailable\"));\n      return;\n    }\n\n    const synth = window.speechSynthesis;\n    synth.cancel();\n\n    const utterance = new SpeechSynthesisUtterance(request.text);\n    const voices = synth.getVoices();\n    if (request.voice) {\n      const match = voices.find((v) => v.name === request.voice);\n      if (match) utterance.voice = match;\n    } else if (voices.length > 0) {\n      utterance.voice = this.options.pickVoice\n        ? this.options.pickVoice(voices)\n        : pickBestVoice(voices);\n    }\n    if (request.rate !== undefined) utterance.rate = request.rate;\n    if (request.pitch !== undefined) utterance.pitch = request.pitch;\n\n    utterance.onend = () => callbacks.onEnd?.();\n    utterance.onerror = (event) => {\n      // A stop()/superseding utterance fires \"canceled\"/\"interrupted\" — that's\n      // a normal end of playback, not an error worth surfacing. `event` is a\n      // SpeechSynthesisErrorEvent, so `.error` is the failure reason.\n      const reason = event.error;\n      if (reason === \"canceled\" || reason === \"interrupted\") {\n        callbacks.onEnd?.();\n      } else {\n        callbacks.onError?.(new Error(reason || \"Speech synthesis failed\"));\n      }\n    };\n\n    // Chrome bug: cancel() immediately followed by speak() can drop rate/pitch.\n    // A short delay lets the engine reset before the new utterance starts.\n    setTimeout(() => {\n      synth.speak(utterance);\n      // `utterance.onstart` is unreliable across browsers — Chrome can leave it\n      // unfired even while actively speaking, which would strand the UI in the\n      // \"loading\" state. Browser TTS has no real async prepare phase, so treat\n      // scheduling as the start; onend/onerror still drive the return to idle.\n      callbacks.onStart?.();\n    }, 50);\n  }\n\n  pause(): void {\n    if (BrowserSpeechEngine.isSupported()) window.speechSynthesis.pause();\n  }\n\n  resume(): void {\n    if (BrowserSpeechEngine.isSupported()) window.speechSynthesis.resume();\n  }\n\n  stop(): void {\n    if (BrowserSpeechEngine.isSupported()) window.speechSynthesis.cancel();\n  }\n}\n","// Read-Aloud Controller\n//\n// Engine-agnostic orchestrator for the per-message \"Read aloud\" action and the\n// auto-speak path. Owns the three things the UI shouldn't: which message is\n// currently active, its playback state, and the single speech engine instance.\n//\n// The button drives a single `toggle(id, request)`; listeners are notified on\n// every state change so the UI can reflect play / pause / resume. The engine is\n// resolved lazily (on first playback, inside the user gesture) via the\n// `resolveEngine` factory, so swapping the browser engine for a hosted one\n// (Runtype or custom) is a config change — this class is unchanged.\n\nimport type { ReadAloudState, SpeechEngine, SpeechRequest } from \"../types\";\n\nexport type ReadAloudListener = (\n  activeId: string | null,\n  state: ReadAloudState\n) => void;\n\nexport class ReadAloudController {\n  private engine: SpeechEngine | null = null;\n  private activeId: string | null = null;\n  private state: ReadAloudState = \"idle\";\n  private listeners = new Set<ReadAloudListener>();\n  // Bumped on every play/stop so a late async engine creation or a stale\n  // utterance callback from a superseded request can't mutate current state.\n  private generation = 0;\n\n  constructor(\n    private resolveEngine: () => SpeechEngine | Promise<SpeechEngine> | null\n  ) {}\n\n  /** Whether the active engine supports pause/resume (vs. stop-only). */\n  get supportsPause(): boolean {\n    return this.engine?.supportsPause ?? true;\n  }\n\n  /** Playback state for a message id (`idle` unless it's the active message). */\n  stateFor(id: string): ReadAloudState {\n    return this.activeId === id ? this.state : \"idle\";\n  }\n\n  /** The message currently being read aloud, if any. */\n  activeMessageId(): string | null {\n    return this.activeId;\n  }\n\n  /** Subscribe to state changes. Returns an unsubscribe function. */\n  onChange(listener: ReadAloudListener): () => void {\n    this.listeners.add(listener);\n    return () => this.listeners.delete(listener);\n  }\n\n  /**\n   * Primary entry point for the button: cycle this message through\n   * play → pause → resume (or play → stop when the engine can't pause), and\n   * start fresh when a different message is requested.\n   */\n  toggle(id: string, request: SpeechRequest): void {\n    if (this.activeId === id) {\n      if (this.state === \"playing\") {\n        if (this.engine?.supportsPause) {\n          this.engine.pause();\n          this.set(id, \"paused\");\n        } else {\n          this.stop();\n        }\n        return;\n      }\n      if (this.state === \"paused\") {\n        this.engine?.resume();\n        this.set(id, \"playing\");\n        return;\n      }\n      if (this.state === \"loading\") {\n        this.stop();\n        return;\n      }\n    }\n    void this.play(id, request);\n  }\n\n  /** Start (or restart) playback for a message, stopping any current playback. */\n  async play(id: string, request: SpeechRequest): Promise<void> {\n    const generation = ++this.generation;\n    this.engine?.stop();\n    this.set(id, \"loading\");\n\n    try {\n      if (!this.engine) {\n        const resolved = await this.resolveEngine();\n        if (generation !== this.generation) return; // superseded mid-resolve\n        if (!resolved) {\n          this.set(null, \"idle\");\n          return;\n        }\n        this.engine = resolved;\n      }\n\n      this.engine.speak(request, {\n        onStart: () => {\n          if (generation === this.generation) this.set(id, \"playing\");\n        },\n        onEnd: () => {\n          if (generation === this.generation) this.set(null, \"idle\");\n        },\n        onError: () => {\n          if (generation === this.generation) this.set(null, \"idle\");\n        },\n      });\n    } catch {\n      if (generation === this.generation) this.set(null, \"idle\");\n    }\n  }\n\n  /** Stop playback and return to idle. */\n  stop(): void {\n    this.generation++;\n    this.engine?.stop();\n    this.set(null, \"idle\");\n  }\n\n  /** Drop the controller and its engine (called on widget teardown). */\n  destroy(): void {\n    this.stop();\n    this.engine?.destroy?.();\n    this.engine = null;\n    this.listeners.clear();\n  }\n\n  private set(id: string | null, state: ReadAloudState): void {\n    this.activeId = state === \"idle\" ? null : id;\n    this.state = state;\n    for (const listener of this.listeners) listener(this.activeId, this.state);\n  }\n}\n","/**\n * Resolve the text that should be spoken for an assistant message.\n *\n * Some flows return an action-format envelope as the message body — e.g.\n * `{\"action\":\"message\",\"text\":\"Hello\"}`, often inside a ```json fence — rather\n * than plain prose. Speaking the raw JSON (or, after fenced-code stripping,\n * nothing at all) is useless, so we first try to extract the human `text` field\n * (mirroring the widget's `defaultJsonActionParser`, which also keys on\n * `.text`), and otherwise fall back to stripping Markdown from the raw content.\n */\nexport function resolveSpeakableText(raw: string): string {\n  if (!raw) return \"\";\n  const actionText = extractActionMessageText(raw);\n  if (actionText !== null) return stripMarkdownForSpeech(actionText);\n  return stripMarkdownForSpeech(raw);\n}\n\n/**\n * If `raw` is (or wraps, in a ```json fence) a JSON object with a string `text`\n * property, return that text; otherwise return `null`. Matches the action\n * envelope shape `{ action, text, ... }` used by the widget's action system.\n */\nexport function extractActionMessageText(raw: string): string | null {\n  let body = raw.trim();\n  const fence = body.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/i);\n  if (fence) body = fence[1].trim();\n  if (!body.startsWith(\"{\")) return null;\n  try {\n    const parsed = JSON.parse(body);\n    if (parsed && typeof parsed === \"object\" && typeof (parsed as { text?: unknown }).text === \"string\") {\n      return (parsed as { text: string }).text;\n    }\n  } catch {\n    // Not valid JSON — fall through to the Markdown path.\n  }\n  return null;\n}\n\n/**\n * Convert Markdown to plain text suitable for text-to-speech.\n *\n * Read-aloud should speak the prose a user sees, not the markup: code fences,\n * backticks, emphasis markers, link/image syntax and raw HTML are all noise\n * when spoken. This is intentionally lightweight (regex-based, no full Markdown\n * parser) — it favours predictable, dependency-free output over perfect\n * fidelity. Speech engines receive the result of this function, never raw\n * Markdown.\n */\nexport function stripMarkdownForSpeech(markdown: string): string {\n  if (!markdown) return \"\";\n  let text = markdown;\n\n  // Fenced code blocks: drop entirely (reading source aloud is noise).\n  text = text.replace(/```[\\s\\S]*?```/g, \" \");\n  text = text.replace(/~~~[\\s\\S]*?~~~/g, \" \");\n\n  // Inline code: keep the inner text, drop the backticks.\n  text = text.replace(/`([^`]+)`/g, \"$1\");\n\n  // Images: speak the alt text only.\n  text = text.replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\");\n\n  // Inline links: speak the link text, drop the URL.\n  text = text.replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, \"$1\");\n\n  // Reference-style links/images: [text][ref] -> text.\n  text = text.replace(/\\[([^\\]]+)\\]\\[[^\\]]*\\]/g, \"$1\");\n\n  // Raw HTML tags.\n  text = text.replace(/<\\/?[a-zA-Z][^>]*>/g, \" \");\n\n  // Line-leading markers: headings, blockquotes, list bullets, ordered items.\n  text = text.replace(/^[ \\t]*#{1,6}[ \\t]+/gm, \"\");\n  text = text.replace(/^[ \\t]*>[ \\t]?/gm, \"\");\n  text = text.replace(/^[ \\t]*[-*+][ \\t]+/gm, \"\");\n  text = text.replace(/^[ \\t]*\\d+\\.[ \\t]+/gm, \"\");\n\n  // Horizontal rules.\n  text = text.replace(/^[ \\t]*([-*_])([ \\t]*\\1){2,}[ \\t]*$/gm, \" \");\n\n  // Emphasis / strikethrough markers (bold before italic so ** is consumed).\n  text = text.replace(/(\\*\\*|__)(.*?)\\1/g, \"$2\");\n  text = text.replace(/(\\*|_)(.*?)\\1/g, \"$2\");\n  text = text.replace(/~~(.*?)~~/g, \"$1\");\n\n  // Decode a few common HTML entities so they aren't spoken literally.\n  text = text\n    .replace(/&amp;/g, \"&\")\n    .replace(/&lt;/g, \"<\")\n    .replace(/&gt;/g, \">\")\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&nbsp;/g, \" \");\n\n  // Collapse whitespace.\n  text = text.replace(/[ \\t]+/g, \" \");\n  text = text.replace(/[ \\t]*\\n[ \\t]*/g, \"\\n\");\n  text = text.replace(/\\n{2,}/g, \"\\n\");\n\n  return text.trim();\n}\n","// Deferred loader for the hosted Runtype TTS read-aloud engine.\n//\n// The engine (`RuntypeSpeechEngine` + the `AudioPlaybackManager` it bundles) is\n// ~4–5 kB and only used when `textToSpeech.provider: 'runtype'` is configured —\n// an opt-in. To keep it out of the CDN payload (`index.global.js`), the IIFE\n// build marks the default `import(\"./runtype-speech-engine\")` below external and\n// `index-global.ts` registers a loader that imports the standalone\n// `runtype-tts.js` chunk from a sibling URL instead. Mirrors how the WebMCP\n// polyfill is deferred (see `setWebMcpPolyfillLoader` in `webmcp-bridge.ts`).\n//\n// In every other build (ESM/CJS main entry, theme-editor) no loader is\n// registered, so the default relative import resolves and the engine is inlined\n// — those bundlers code-split or have headroom, so there's no runtime fetch for\n// npm consumers.\n\nimport type { RuntypeSpeechEngine } from \"./runtype-speech-engine\";\nimport type { FallbackSpeechEngine } from \"./fallback-speech-engine\";\n\n/** The slice of the engine chunk the session consumes. */\nexport type RuntypeTtsModule = {\n  RuntypeSpeechEngine: typeof RuntypeSpeechEngine;\n  FallbackSpeechEngine: typeof FallbackSpeechEngine;\n};\n\nlet loader: (() => Promise<RuntypeTtsModule>) | null = null;\n\n/**\n * Override how the Runtype TTS engine module is obtained. By default the session\n * does `import(\"./runtype-tts-entry\")`, which bundlers resolve/inline. The\n * IIFE/CDN entry registers a loader that imports the self-contained\n * `runtype-tts.js` chunk from a URL derived from the widget script's own `src`.\n * Pass `null` to restore the default (used by tests).\n */\nexport const setRuntypeTtsLoader = (\n  l: (() => Promise<RuntypeTtsModule>) | null,\n): void => {\n  loader = l;\n};\n\n/** Resolve the Runtype TTS engine module (registered loader, else inlined import). */\nexport const loadRuntypeTts = (): Promise<RuntypeTtsModule> =>\n  loader ? loader() : import(\"./runtype-tts-entry\");\n","import { AgentWidgetClient, type SSEEventCallback } from \"./client\";\nimport { isWebMcpToolName } from \"./webmcp-bridge\";\nimport {\n  SUGGEST_REPLIES_TOOL_NAME,\n  suggestRepliesToolResult,\n} from \"./suggest-replies-tool\";\nimport {\n  AgentWidgetConfig,\n  AgentWidgetEvent,\n  AgentWidgetMessage,\n  AgentWidgetApproval,\n  AgentWidgetApprovalDecisionOptions,\n  WebMcpConfirmInfo,\n  AgentExecutionState,\n  ResumableHandle,\n  ClientSession,\n  ContentPart,\n  InjectMessageOptions,\n  InjectAssistantMessageOptions,\n  InjectUserMessageOptions,\n  InjectSystemMessageOptions,\n  InjectComponentDirectiveOptions,\n  PersonaArtifactRecord,\n  PersonaArtifactManualUpsert\n} from \"./types\";\nimport {\n  generateUserMessageId,\n  generateAssistantMessageId\n} from \"./utils/message-id\";\nimport { IMAGE_ONLY_MESSAGE_FALLBACK_TEXT } from \"./utils/content\";\nimport {\n  buildArtifactRefRawContent,\n  resolveArtifactDisplayMode\n} from \"./utils/artifact-display\";\nimport type {\n  VoiceProvider,\n  VoiceStatus,\n  VoiceConfig,\n  ReadAloudState,\n  SpeechEngine\n} from \"./types\";\nimport {\n  createVoiceProvider,\n  isVoiceSupported,\n  BrowserSpeechEngine,\n  pickBestVoice,\n  ReadAloudController,\n  type ReadAloudListener\n} from \"./voice\";\nimport { resolveSpeakableText } from \"./utils/speech-text\";\nimport { loadRuntypeTts } from \"./voice/runtype-tts-loader\";\n// Type-only (erased at build): the runtime reconnect machinery is reached via a\n// dynamic import in `beginReconnect`, so it never lands in bundles that don't\n// opt into durable reconnect (see ./session-reconnect.ts).\nimport type {\n  ReconnectController,\n  ReconnectHost,\n} from \"./session-reconnect\";\n\nexport type AgentWidgetSessionStatus =\n  | \"idle\"\n  | \"connecting\"\n  | \"connected\"\n  | \"error\"\n  // Durable-session reconnect states. `paused`: a durable stream dropped and a\n  // reconnect is pending. `resuming`: a reconnect attempt is in flight. While\n  // in either, the in-progress assistant bubble stays open (not finalized) and\n  // `streaming` stays true so the typing indicator persists.\n  | \"paused\"\n  | \"resuming\";\n\n/**\n * Config fields the `AgentWidgetClient` reads to shape the connection and each\n * request. When NONE of these change, `updateConfig` can refresh the live\n * client in place (preserving the WebMCP bridge and any in-flight stream/resume)\n * instead of swapping in a fresh client and tearing down WebMCP state. Fields\n * outside this list (theme, copy, layout, suggestionChips, iterationDisplay,\n * postprocessMessage, feature display toggles, …) are display-only and safe to\n * apply mid-turn: which is what a self-styling widget needs when a `webmcp:*`\n * theme tool re-renders the widget while the agent's turn is still streaming.\n *\n * Compared by identity (`!==`): primitives by value, functions/objects by\n * reference. A consumer that rebuilds these objects on every render simply\n * takes the (still-correct) full-rebuild path. The default is therefore safe:\n * anything not explicitly listed here can never strand a paused turn.\n */\nconst CONNECTION_CONFIG_KEYS = [\n  \"apiUrl\",\n  \"clientToken\",\n  \"flowId\",\n  \"agentId\",\n  \"target\",\n  \"targetProviders\",\n  \"agent\",\n  \"agentOptions\",\n  \"headers\",\n  \"getHeaders\",\n  \"webmcp\",\n  \"streamParser\",\n  \"parserType\",\n  \"contextProviders\",\n  \"requestMiddleware\",\n  \"customFetch\",\n  \"parseSSEEvent\",\n  \"onSessionInit\",\n  \"onSessionExpired\",\n  \"getStoredSessionId\",\n  \"setStoredSessionId\",\n] as const satisfies ReadonlyArray<keyof AgentWidgetConfig>;\n\nfunction connectionConfigChanged(\n  prev: AgentWidgetConfig,\n  next: AgentWidgetConfig,\n): boolean {\n  return CONNECTION_CONFIG_KEYS.some((key) => prev[key] !== next[key]);\n}\n\ntype SessionCallbacks = {\n  onMessagesChanged: (messages: AgentWidgetMessage[]) => void;\n  onStatusChanged: (status: AgentWidgetSessionStatus) => void;\n  onStreamingChanged: (streaming: boolean) => void;\n  onError?: (error: Error) => void;\n  onVoiceStatusChanged?: (status: VoiceStatus) => void;\n  onArtifactsState?: (state: {\n    artifacts: PersonaArtifactRecord[];\n    selectedId: string | null;\n  }) => void;\n  /**\n   * Durable-session reconnect lifecycle, surfaced so the UI can map it to the\n   * public `stream:paused` / `stream:resuming` / `stream:resumed` controller\n   * events. `paused` fires once on the drop, `resuming` once per attempt,\n   * `resumed` when a reconnect reaches its terminal.\n   */\n  onReconnect?: (event: {\n    phase: \"paused\" | \"resuming\" | \"resumed\";\n    handle: ResumableHandle;\n    attempt?: number;\n  }) => void;\n};\n\n/**\n * Build the user-facing content shown when a dispatch fails before any\n * assistant content streamed back. This fires on real network/server errors\n * (connection refused, CORS, 4xx/5xx, malformed stream): not just an\n * un-wired proxy, so the copy stays honest about the failure and surfaces the\n * underlying reason to help with debugging.\n *\n * Callers can override the copy via `config.errorMessage` (a static string or\n * a function of the error). An override that returns an empty string yields \"\"\n * here, which the caller treats as \"suppress the fallback bubble\".\n */\nfunction buildDispatchErrorContent(\n  error: unknown,\n  override?: AgentWidgetConfig[\"errorMessage\"]\n): string {\n  const err = error instanceof Error ? error : new Error(String(error));\n\n  if (typeof override === \"string\") return override;\n  if (typeof override === \"function\") return override(err);\n\n  const base =\n    \"Sorry: I couldn't reach the assistant. The chat service didn't respond. Please check that your proxy or backend is running and reachable, then try again.\";\n  return err.message ? `${base}\\n\\n_Details: ${err.message}_` : base;\n}\n\nconst buildWebMcpErrorResult = (message: string) => ({\n  isError: true,\n  content: [{ type: \"text\" as const, text: message }],\n});\n\nconst getWebMcpErrorMessage = (\n  error: unknown,\n  fallback = \"WebMCP tool execution failed.\",\n): string => {\n  if (error instanceof Error && error.message) return error.message;\n  if (typeof error === \"string\" && error) return error;\n  return fallback;\n};\n\n/**\n * Tool names whose `step_await` the widget resolves automatically (no user\n * pill click): `webmcp:*` page tools and the built-in fire-and-forget\n * `suggest_replies`. These share the await-batch / dedupe / resume machinery;\n * `ask_user_question` is NOT one of them: it blocks on the answer sheet.\n */\nconst isAutoResolvedLocalToolName = (name: string): boolean =>\n  isWebMcpToolName(name) || name === SUGGEST_REPLIES_TOOL_NAME;\n\nexport class AgentWidgetSession {\n  private client: AgentWidgetClient;\n  private messages: AgentWidgetMessage[];\n  private status: AgentWidgetSessionStatus = \"idle\";\n  private streaming = false;\n  private abortController: AbortController | null = null;\n  private sequenceCounter = Date.now();\n  \n  // Client token session management\n  private clientSession: ClientSession | null = null;\n\n  // Agent execution state\n  private agentExecution: AgentExecutionState | null = null;\n\n  // ── Durable-session reconnect ──────────────────────────────────\n  // The in-flight resume handle for the durable lane (any resumable agent\n  // execution the backend persists and can replay, e.g. Claude Managed agents\n  // or async/background runs). Created when a stream that carries SSE `id:`\n  // lines first yields a cursor (and an executionId is known); advanced as the\n  // cursor climbs; cleared on a graceful terminal, a terminal error, or any\n  // teardown. Drives both in-session reconnect and host persistence\n  // (`onExecutionState`).\n  private resumable: ResumableHandle | null = null;\n  // The assistant message id of the current turn, so a reconnect keeps filling\n  // the SAME bubble instead of opening a new one.\n  private activeAssistantMessageId: string | null = null;\n  // True while a reconnect run is active (guards against re-entry from a second\n  // drop and tells the dispatch/connectStream catches to stay quiet).\n  private reconnecting = false;\n  // The reconnect orchestration (backoff loop, wake listeners, give-up\n  // finalizer) lives in a lazily-imported module so it stays out of bundles\n  // that never opt into durable reconnect. Created on the first reconnect.\n  private reconnectController: ReconnectController | null = null;\n  private reconnectControllerPromise: Promise<ReconnectController> | null = null;\n  // Trailing-edge throttle for `onExecutionState` so it isn't fired per delta.\n  private executionStateTimer: ReturnType<typeof setTimeout> | null = null;\n\n  private artifacts = new Map<string, PersonaArtifactRecord>();\n  private selectedArtifactId: string | null = null;\n\n  // WebMCP dedupe: keys are `${executionId}:${toolCallId}` so they're\n  // naturally scoped to a single dispatch. A later dispatch (new executionId)\n  // that happens to recycle a `toolCall.id` never collides with prior entries,\n  // and a stale re-emit from an in-flight prior dispatch stays blocked because\n  // its executionId is still in the set.\n  //\n  //   webMcpInflightKeys: currently executing; cleared on completion of\n  //                         EITHER /resume success OR /resume throw. Blocks\n  //                         concurrent re-fire during the resolve round-trip.\n  //   webMcpResolvedKeys: /resume HTTP returned 2xx; not cleared on a new\n  //                         dispatch (executionId scoping makes that\n  //                         unnecessary). Blocks stale step_await re-emits\n  //                         for the same execution.\n  //\n  // If `/resume` throws (network error, server 5xx), we DO want a retry path:\n  // the dispatch is recoverable. Such a tool stays in neither set, so a\n  // subsequent re-emit will re-trigger.\n  private webMcpInflightKeys: Set<string> = new Set();\n  private webMcpResolvedKeys: Set<string> = new Set();\n  // Per-resolve AbortControllers, kept in a set so multiple `webmcp:*`\n  // step_await resolves in one turn never abort one another. The shared\n  // `this.abortController` is intentionally NOT used by resolveWebMcpToolCall:\n  // in a CHAINED turn (tool A → /resume → tool B, where the server emits B's\n  // step_await inside A's resume SSE stream) the shared controller is still\n  // piping A's resume stream: the very stream that just delivered B. Aborting\n  // it mid-chain (the prior shared-controller pre-abort) tore that stream down,\n  // so B never reached execute() and its /resume was never POSTed, pausing the\n  // dispatch forever. cancel(), clearMessages(), hydrateMessages(), and\n  // sendMessage() iterate this set to tear every in-flight resolve down on a\n  // real stop / new turn.\n  private webMcpResolveControllers: Set<AbortController> = new Set();\n  // Bumped on every teardown / new-turn boundary (cancel, clearMessages,\n  // hydrateMessages, sendMessage). A resolveWebMcpToolCall deferred via\n  // queueMicrotask captures the epoch at queue time and bails if it changed,\n  // so a resolve queued just before a teardown can't escape it by installing a\n  // fresh controller after the set was already cleared.\n  private webMcpEpoch = 0;\n  // WebMCP native approval-bubble gate. When no custom `webmcp.onConfirm` is\n  // supplied, the bridge's confirm handler routes here: we inject an\n  // approval-variant message and park the bridge on a Promise that resolves\n  // when the user clicks Approve/Deny (see requestWebMcpApproval /\n  // resolveWebMcpApproval). Resolvers are keyed by the approval message id.\n  private webMcpApprovalResolvers: Map<string, (approved: boolean) => void> =\n    new Map();\n  private webMcpApprovalSeq = 0;\n  // Parallel local-tool batching (core#3878). A single model turn can emit\n  // multiple `step_await(local_tool_required)` events for ONE paused\n  // executionId: including two PARALLEL calls to the SAME tool (\"add SHOE-001\n  // and SHOE-007\"). Those collapse to an identical `toolId`/`index` and differ\n  // only by the per-call `webMcpToolCallId`. We collect all awaits for an\n  // executionId that arrive in the same stream tick, then post ONE `/resume`\n  // keyed by `webMcpToolCallId`: NOT one `/resume` per tool keyed by name\n  // (which collides for same-tool calls, and whose concurrent posts on one\n  // execution raced → the second 404'd → the turn hung). Keyed by executionId;\n  // `seen` dedupes duplicate step_await re-emits within a batch. Cleared on\n  // every teardown via `abortWebMcpResolves`.\n  private webMcpAwaitBatches: Map<\n    string,\n    { snapshots: AgentWidgetMessage[]; seen: Set<string> }\n  > = new Map();\n\n  // Voice support\n  private voiceProvider: VoiceProvider | null = null;\n  private voiceActive = false;\n  private voiceStatus: VoiceStatus = 'disconnected';\n\n  constructor(\n    private config: AgentWidgetConfig = {},\n    private callbacks: SessionCallbacks\n  ) {\n    this.messages = [...(config.initialMessages ?? [])].map((message) => ({\n      ...message,\n      sequence: message.sequence ?? this.nextSequence()\n    }));\n    this.messages = this.sortMessages(this.messages);\n    this.client = new AgentWidgetClient(config);\n    this.wireDefaultWebMcpConfirm();\n\n    // Hydrate artifacts from config (mirrors `initialMessages`). Restored\n    // records are forced to `status: \"complete\"`: a mid-stream artifact should\n    // never reappear after a refresh with its skeleton still showing.\n    for (const rec of config.initialArtifacts ?? []) {\n      this.artifacts.set(rec.id, { ...rec, status: \"complete\" });\n    }\n    if (config.initialSelectedArtifactId != null) {\n      this.selectedArtifactId = config.initialSelectedArtifactId;\n    }\n\n    if (this.messages.length) {\n      this.callbacks.onMessagesChanged([...this.messages]);\n    }\n    if (this.artifacts.size > 0) {\n      this.emitArtifactsState();\n    }\n    this.callbacks.onStatusChanged(this.status);\n    this.prefetchRuntypeTts();\n  }\n\n  /**\n   * Warm the deferred Runtype TTS engine chunk at init so it's resolved before\n   * the first \"Read aloud\" click. Module fetch only — no AudioContext (that\n   * still waits for the user gesture on first playback). In the IIFE/CDN build\n   * this fetches the standalone `runtype-tts.js`; in bundler builds the module\n   * is already inlined, so this is a cheap no-op. Errors are swallowed: a failed\n   * prefetch just means the chunk loads on first click instead (or, on a hard\n   * failure, the browser-voice fallback kicks in).\n   */\n  private prefetchRuntypeTts(): void {\n    const tts = this.config.textToSpeech;\n    if (tts?.provider !== \"runtype\" || tts.createEngine) return;\n    // Only warm the chunk when the engine will actually be built (same gate as\n    // createSpeechEngine) — an incomplete config (e.g. a proxy demo with no\n    // clientToken) falls back to the browser voice, so the fetch would be wasted.\n    const host = tts.host ?? this.config.apiUrl;\n    const agentId =\n      tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId ?? this.config.agentId;\n    if (!host || !agentId || !this.config.clientToken) return;\n    void loadRuntypeTts().catch(() => {});\n  }\n\n  /**\n   * Set callback for capturing raw SSE events (forwards to client)\n   */\n  public setSSEEventCallback(callback: SSEEventCallback): void {\n    this.client.setSSEEventCallback(callback);\n  }\n\n  /**\n   * Check if running in client token mode\n   */\n  public isClientTokenMode(): boolean {\n    return this.client.isClientTokenMode();\n  }\n\n  /**\n   * Check if running in agent execution mode\n   */\n  public isAgentMode(): boolean {\n    return this.client.isAgentMode();\n  }\n\n  /**\n   * Get current agent execution state (if in agent mode)\n   */\n  public getAgentExecution(): AgentExecutionState | null {\n    return this.agentExecution;\n  }\n\n  /**\n   * Check if an agent execution is currently running\n   */\n  public isAgentExecuting(): boolean {\n    return this.agentExecution?.status === 'running';\n  }\n\n  /**\n   * Check if voice is supported\n   */\n  public isVoiceSupported(): boolean {\n    return isVoiceSupported(this.config.voiceRecognition?.provider);\n  }\n\n  /**\n   * Check if voice is currently active\n   */\n  public isVoiceActive(): boolean {\n    return this.voiceActive;\n  }\n\n  /**\n   * Get current voice status\n   */\n  public getVoiceStatus(): VoiceStatus {\n    return this.voiceStatus;\n  }\n\n  /**\n   * Get the voice interruption mode from the provider (none/cancel/barge-in)\n   */\n  public getVoiceInterruptionMode(): \"none\" | \"cancel\" | \"barge-in\" {\n    if (this.voiceProvider?.getInterruptionMode) {\n      return this.voiceProvider.getInterruptionMode();\n    }\n    return \"none\";\n  }\n\n  /**\n   * Stop voice playback / cancel in-flight request without starting recording.\n   * Returns to idle state.\n   */\n  public stopVoicePlayback(): void {\n    if (this.voiceProvider?.stopPlayback) {\n      this.voiceProvider.stopPlayback();\n    }\n  }\n\n  /** Returns true if the barge-in mic stream is alive (hot mic between turns) */\n  public isBargeInActive(): boolean {\n    return this.voiceProvider?.isBargeInActive?.() ?? false;\n  }\n\n  /** Tear down the barge-in mic pipeline: \"hang up\" the always-on mic */\n  public async deactivateBargeIn(): Promise<void> {\n    if (this.voiceProvider?.deactivateBargeIn) {\n      await this.voiceProvider.deactivateBargeIn();\n    }\n  }\n\n  // Pending placeholder IDs for Runtype two-phase voice flow\n  private pendingVoiceUserMessageId: string | null = null;\n  private pendingVoiceAssistantMessageId: string | null = null;\n\n  // Track message IDs where the Runtype provider already played TTS audio\n  // so browser TTS doesn't double-speak them\n  private ttsSpokenMessageIds = new Set<string>();\n\n  // Owns the per-message \"Read aloud\" action and the auto-speak path: which\n  // message is active, its play/pause state, and the speech engine. The engine\n  // is resolved lazily on first playback (inside the user gesture) so a hosted\n  // engine via `textToSpeech.createEngine` plugs in without changing this class.\n  private readAloud = new ReadAloudController(() => this.createSpeechEngine());\n\n  /**\n   * Resolve the speech engine behind read-aloud (and auto-speak):\n   *   1. An explicit `textToSpeech.createEngine` always wins (full BYO control).\n   *   2. `provider: 'runtype'` builds the hosted {@link RuntypeSpeechEngine}\n   *      from the widget config (host = `apiUrl`, `agentId`, `clientToken`),\n   *      wrapped in a browser {@link FallbackSpeechEngine} unless\n   *      `browserFallback: false` — so a missing endpoint or transient failure\n   *      degrades to the browser voice instead of erroring, and auto-upgrades to\n   *      Runtype voices the day the endpoint ships.\n   *   3. Otherwise the browser Web Speech API engine.\n   */\n  private createSpeechEngine(): SpeechEngine | Promise<SpeechEngine> | null {\n    const tts = this.config.textToSpeech;\n    if (tts?.createEngine) return tts.createEngine();\n\n    const browser = BrowserSpeechEngine.isSupported()\n      ? new BrowserSpeechEngine({ pickVoice: tts?.pickVoice })\n      : null;\n\n    if (tts?.provider === \"runtype\") {\n      const host = tts.host ?? this.config.apiUrl;\n      const agentId =\n        tts.agentId ?? this.config.voiceRecognition?.provider?.runtype?.agentId ?? this.config.agentId;\n      const clientToken = this.config.clientToken;\n      const wantFallback = tts.browserFallback !== false;\n\n      if (host && agentId && clientToken) {\n        // Lazy-load the Runtype engine through the deferred loader: the IIFE/CDN\n        // build serves it from the standalone `runtype-tts.js` chunk (kept out of\n        // `index.global.js`), while bundler consumers get it inlined. Resolved on\n        // first playback (inside the user gesture); `createSpeechEngine` may\n        // return a Promise. Prefetched at init (see `prefetchRuntypeTts`) so the\n        // chunk is warm before the first click.\n        return loadRuntypeTts().then(\n          ({ RuntypeSpeechEngine, FallbackSpeechEngine }) => {\n            const runtype = new RuntypeSpeechEngine({\n              host,\n              agentId,\n              clientToken,\n              voice: tts.voice,\n              prebufferMs: tts.prebufferMs,\n              createPlaybackEngine: tts.createPlaybackEngine,\n            });\n            return wantFallback && browser\n              ? new FallbackSpeechEngine(runtype, browser, {\n                  onFallback: (error) =>\n                    console.warn(\n                      `[persona] Runtype read-aloud failed; using browser voice. ${error.message}`,\n                    ),\n                })\n              : runtype;\n          },\n        );\n      }\n\n      // Runtype was requested but the config is incomplete (no agentId/token —\n      // e.g. a proxy-backed demo). Never break the button: use the browser\n      // voice. Only warn when intent is clear (a clientToken is present) so a\n      // plain proxy demo stays quiet.\n      if (wantFallback && browser) {\n        if (clientToken) {\n          console.warn(\n            \"[persona] textToSpeech.provider 'runtype' is missing an agentId; using the browser voice. Set textToSpeech.agentId (or voiceRecognition.provider.runtype.agentId).\",\n          );\n        }\n        return browser;\n      }\n    }\n\n    return browser;\n  }\n\n  /**\n   * Setup voice recognition with the given configuration\n   */\n  public setupVoice(config?: VoiceConfig) {\n    try {\n      const voiceConfig = config || this.getVoiceConfigFromConfig();\n      if (!voiceConfig) {\n        throw new Error('Voice configuration not provided');\n      }\n\n      this.voiceProvider = createVoiceProvider(voiceConfig);\n\n      // Read configurable text from widget config\n      const voiceRecognitionConfig = this.config.voiceRecognition ?? {};\n      const processingErrorText = voiceRecognitionConfig.processingErrorText ?? 'Voice processing failed. Please try again.';\n\n      // STT-style providers (browser + bring-your-own `custom`) deliver a final\n      // transcript that we send as a normal user message: the agent then runs\n      // via the standard SSE chat path. Only the realtime `runtype` provider is\n      // excluded here: it owns the whole turn and drives onTranscript below.\n      this.voiceProvider.onResult((result) => {\n        if (result.provider !== 'runtype') {\n          if (result.text && result.text.trim()) {\n            this.sendMessage(result.text, { viaVoice: true });\n          }\n        }\n      });\n\n      // Realtime (runtype) voice: drive the chat thread from streaming\n      // transcript frames. Live interim user text grows in place; the user\n      // message finalizes immediately on transcript_final{user}; the assistant\n      // reply lands (a single block, synced with audio) on its final frame.\n      // In-flight bubbles carry voiceProcessing=true so consumers can style\n      // them via messageTransform; it clears once the text is final.\n      if (this.voiceProvider.onTranscript) {\n        this.voiceProvider.onTranscript((role, text, isFinal) => {\n          if (role === 'user') {\n            if (!this.pendingVoiceUserMessageId) {\n              const msg = this.injectMessage({\n                role: 'user',\n                content: text,\n                streaming: false,\n                voiceProcessing: !isFinal\n              });\n              this.pendingVoiceUserMessageId = msg.id;\n            } else {\n              this.upsertMessage({\n                id: this.pendingVoiceUserMessageId,\n                role: 'user',\n                content: text,\n                createdAt: new Date().toISOString(),\n                streaming: false,\n                voiceProcessing: !isFinal\n              });\n            }\n\n            if (isFinal) {\n              // User finished: the agent is now thinking. Release the user\n              // bubble (a new interim starts a fresh turn) and show a typing\n              // indicator in a fresh assistant placeholder.\n              this.pendingVoiceUserMessageId = null;\n              const assistantMsg = this.injectMessage({\n                role: 'assistant',\n                content: '',\n                streaming: true,\n                voiceProcessing: true\n              });\n              this.pendingVoiceAssistantMessageId = assistantMsg.id;\n              this.setStreaming(true);\n            }\n          } else {\n            // assistant: runtype sends a single final; the isFinal=false path\n            // is reserved for delta-streaming providers (future BYO).\n            if (this.pendingVoiceAssistantMessageId) {\n              this.upsertMessage({\n                id: this.pendingVoiceAssistantMessageId,\n                role: 'assistant',\n                content: text,\n                createdAt: new Date().toISOString(),\n                streaming: !isFinal,\n                voiceProcessing: !isFinal\n              });\n            } else {\n              const msg = this.injectMessage({\n                role: 'assistant',\n                content: text,\n                streaming: !isFinal,\n                voiceProcessing: !isFinal\n              });\n              this.pendingVoiceAssistantMessageId = msg.id;\n            }\n\n            if (isFinal) {\n              // The provider plays this reply's audio: mark it spoken so\n              // browser TTS doesn't double-speak when streaming ends. Must run\n              // BEFORE setStreaming(false), which triggers the TTS check.\n              if (this.pendingVoiceAssistantMessageId) {\n                this.ttsSpokenMessageIds.add(this.pendingVoiceAssistantMessageId);\n              }\n              this.setStreaming(false);\n              this.pendingVoiceAssistantMessageId = null;\n            }\n          }\n        });\n      }\n\n      // Surface per-turn latency metrics to the optional config hook.\n      if (this.voiceProvider.onMetrics) {\n        this.voiceProvider.onMetrics((metrics) => {\n          this.config.voiceRecognition?.onMetrics?.(metrics);\n        });\n      }\n\n      this.voiceProvider.onError((error) => {\n        console.error('Voice error:', error);\n\n        // If error occurs while placeholders are pending, update assistant with error text\n        if (this.pendingVoiceAssistantMessageId) {\n          this.upsertMessage({\n            id: this.pendingVoiceAssistantMessageId,\n            role: 'assistant',\n            content: processingErrorText,\n            createdAt: new Date().toISOString(),\n            streaming: false,\n            voiceProcessing: false\n          });\n          this.setStreaming(false);\n          this.pendingVoiceUserMessageId = null;\n          this.pendingVoiceAssistantMessageId = null;\n        }\n      });\n\n      this.voiceProvider.onStatusChange((status) => {\n        this.voiceStatus = status;\n        this.voiceActive = status === 'listening';\n        this.callbacks.onVoiceStatusChanged?.(status);\n      });\n\n      this.voiceProvider.connect();\n\n    } catch (error) {\n      console.error('Failed to setup voice:', error);\n    }\n  }\n\n  /**\n   * Toggle voice recognition on/off\n   */\n  public async toggleVoice() {\n    if (!this.voiceProvider) {\n      console.error('Voice not configured');\n      return;\n    }\n\n    if (this.voiceActive) {\n      await this.voiceProvider.stopListening();\n    } else {\n      // Stop any in-progress TTS so the mic doesn't pick it up\n      this.stopSpeaking();\n      try {\n        await this.voiceProvider.startListening();\n      } catch (error) {\n        console.error('Failed to start voice:', error);\n      }\n    }\n  }\n\n  /**\n   * Cleanup voice resources\n   */\n  public cleanupVoice() {\n    if (this.voiceProvider) {\n      this.voiceProvider.disconnect();\n      this.voiceProvider = null;\n    }\n    this.voiceActive = false;\n    this.voiceStatus = 'disconnected';\n  }\n\n  /**\n   * Extract voice configuration from widget config\n   */\n  private getVoiceConfigFromConfig(): VoiceConfig | undefined {\n    if (!this.config.voiceRecognition?.provider) {\n      return undefined;\n    }\n    \n    const providerConfig = this.config.voiceRecognition.provider;\n    \n    switch (providerConfig.type) {\n      case 'runtype':\n        return {\n          type: 'runtype',\n          runtype: {\n            agentId: providerConfig.runtype?.agentId ?? this.config.agentId ?? '',\n            // Default credentials/endpoint from the widget config so the minimum\n            // voice config collapses to just `agentId`.\n            clientToken: providerConfig.runtype?.clientToken ?? this.config.clientToken,\n            host: providerConfig.runtype?.host ?? this.config.apiUrl,\n            voiceId: providerConfig.runtype?.voiceId,\n            createPlaybackEngine: providerConfig.runtype?.createPlaybackEngine\n          }\n        };\n      \n      case 'browser':\n        return {\n          type: 'browser',\n          browser: {\n            language: providerConfig.browser?.language || 'en-US',\n            continuous: providerConfig.browser?.continuous\n          }\n        };\n\n      case 'custom':\n        // Bring-your-own provider: pass the instance/factory straight through\n        // to the factory, which resolves and validates it.\n        return {\n          type: 'custom',\n          custom: providerConfig.custom\n        };\n\n      default:\n        return undefined;\n    }\n  }\n\n  /**\n   * Initialize the client session (for client token mode).\n   * This is called automatically on first message, but can be called\n   * explicitly to pre-initialize the session and get config from server.\n   */\n  public async initClientSession(): Promise<ClientSession | null> {\n    if (!this.isClientTokenMode()) {\n      return null;\n    }\n    \n    try {\n      const session = await this.client.initSession();\n      this.setClientSession(session);\n      return session;\n    } catch (error) {\n      this.callbacks.onError?.(\n        error instanceof Error ? error : new Error(String(error))\n      );\n      return null;\n    }\n  }\n\n  /**\n   * Set the client session after initialization\n   */\n  public setClientSession(session: ClientSession): void {\n    this.clientSession = session;\n    \n    // Optionally add welcome message from session config\n    if (session.config.welcomeMessage && this.messages.length === 0) {\n      const welcomeMessage: AgentWidgetMessage = {\n        id: `welcome-${Date.now()}`,\n        role: \"assistant\",\n        content: session.config.welcomeMessage,\n        createdAt: new Date().toISOString(),\n        sequence: this.nextSequence()\n      };\n      this.appendMessage(welcomeMessage);\n    }\n  }\n\n  /**\n   * Get current client session\n   */\n  public getClientSession(): ClientSession | null {\n    return this.clientSession ?? this.client.getClientSession();\n  }\n\n  /**\n   * Check if session is valid and not expired\n   */\n  public isSessionValid(): boolean {\n    const session = this.getClientSession();\n    if (!session) return false;\n    return new Date() < session.expiresAt;\n  }\n\n  /**\n   * Clear session (on expiry or error)\n   */\n  public clearClientSession(): void {\n    this.clientSession = null;\n    this.client.clearClientSession();\n  }\n\n  /**\n   * Get the underlying client instance (for advanced use cases like feedback)\n   */\n  public getClient(): AgentWidgetClient {\n    return this.client;\n  }\n\n  /**\n   * Submit message feedback (upvote, downvote, or copy) to the API.\n   * Only available in client token mode.\n   * \n   * @param messageId - The ID of the message to provide feedback for\n   * @param type - The feedback type: 'upvote', 'downvote', or 'copy'\n   */\n  public async submitMessageFeedback(\n    messageId: string,\n    type: 'upvote' | 'downvote' | 'copy'\n  ): Promise<void> {\n    return this.client.submitMessageFeedback(messageId, type);\n  }\n\n  /**\n   * Submit CSAT (Customer Satisfaction) feedback to the API.\n   * Only available in client token mode.\n   * \n   * @param rating - Rating from 1 to 5\n   * @param comment - Optional comment\n   */\n  public async submitCSATFeedback(rating: number, comment?: string): Promise<void> {\n    return this.client.submitCSATFeedback(rating, comment);\n  }\n\n  /**\n   * Submit NPS (Net Promoter Score) feedback to the API.\n   * Only available in client token mode.\n   * \n   * @param rating - Rating from 0 to 10\n   * @param comment - Optional comment\n   */\n  public async submitNPSFeedback(rating: number, comment?: string): Promise<void> {\n    return this.client.submitNPSFeedback(rating, comment);\n  }\n\n  public updateConfig(next: AgentWidgetConfig) {\n    const merged = { ...this.config, ...next };\n\n    // Connection/request-shaping change (apiUrl, clientToken, webmcp, headers,\n    // parser, agent, …) → full client rebuild. UI-only change (theme, copy,\n    // layout, suggestions, …) → refresh in place so the live stream, WebMCP\n    // bridge, and any in-flight resolve survive. The latter is what makes a\n    // self-styling widget work: a `webmcp:*` theme tool mutates config and\n    // re-renders mid-turn; recreating the client there would abort the very\n    // turn that's restyling the widget and strand the paused execution.\n    if (!connectionConfigChanged(this.config, merged)) {\n      this.config = merged;\n      this.client.updateConfig(merged);\n      return;\n    }\n\n    // Replacing the client invalidates every in-flight WebMCP resolve, buffered\n    // parallel-await batch, and pending approval bubble tied to the OLD client/\n    // session. Tear them down BEFORE the swap (the new client has no session\n    // yet) so a deferred batch flush or a parked confirm can't fire against the\n    // fresh client: in client-token mode that would POST /resume without a\n    // valid sessionId and strand the paused turn. Mirrors clearMessages' WebMCP\n    // reset; the client swap already abandons any in-flight stream regardless.\n    this.abortWebMcpResolves();\n    this.webMcpInflightKeys.clear();\n    this.webMcpResolvedKeys.clear();\n    const prevSSECallback = this.client.getSSEEventCallback();\n    this.config = merged;\n    this.client = new AgentWidgetClient(this.config);\n    this.wireDefaultWebMcpConfirm();\n    if (prevSSECallback) {\n      this.client.setSSEEventCallback(prevSSECallback);\n    }\n  }\n\n  public getMessages() {\n    return [...this.messages];\n  }\n\n  public getStatus() {\n    return this.status;\n  }\n\n  public isStreaming() {\n    return this.streaming;\n  }\n\n  /**\n   * @deprecated Use injectMessage() instead.\n   * Injects a raw event into the session event handler.\n   */\n  public injectTestEvent(event: AgentWidgetEvent) {\n    this.handleEvent(event);\n  }\n\n  /**\n   * Inject a message into the conversation.\n   * This is the primary API for adding messages programmatically.\n   *\n   * Supports dual-content where the displayed content differs from what the LLM receives.\n   *\n   * @param options - Message injection options including dual-content support\n   * @returns The created message object\n   *\n   * @example\n   * // Same content for user and LLM\n   * session.injectMessage({\n   *   role: 'assistant',\n   *   content: 'Here are the search results...'\n   * });\n   *\n   * @example\n   * // Different content for user and LLM (redaction)\n   * session.injectMessage({\n   *   role: 'assistant',\n   *   content: '**Found 3 products:**\\n- iPhone 15 Pro ($1,199)\\n- iPhone 15 ($999)',\n   *   llmContent: '[Search results: 3 iPhone products, $799-$1199]'\n   * });\n   */\n  public injectMessage(options: InjectMessageOptions): AgentWidgetMessage {\n    const {\n      role,\n      content,\n      llmContent,\n      contentParts,\n      id,\n      createdAt,\n      sequence,\n      streaming = false,\n      voiceProcessing,\n      rawContent\n    } = options;\n\n    // Generate appropriate ID based on role\n    const messageId =\n      id ??\n      (role === \"user\"\n        ? generateUserMessageId()\n        : role === \"assistant\"\n          ? generateAssistantMessageId()\n          : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`);\n\n    const message: AgentWidgetMessage = {\n      id: messageId,\n      role,\n      content,\n      createdAt: createdAt ?? new Date().toISOString(),\n      sequence: sequence ?? this.nextSequence(),\n      streaming,\n      // Only include optional fields if provided\n      ...(llmContent !== undefined && { llmContent }),\n      ...(contentParts !== undefined && { contentParts }),\n      ...(voiceProcessing !== undefined && { voiceProcessing }),\n      ...(rawContent !== undefined && { rawContent })\n    };\n\n    // Use upsert to handle both new messages and updates (streaming)\n    this.upsertMessage(message);\n\n    return message;\n  }\n\n  /**\n   * Convenience method for injecting assistant messages.\n   * Role defaults to 'assistant'.\n   *\n   * @example\n   * // Simple assistant message\n   * session.injectAssistantMessage({\n   *   content: 'Here are your search results...'\n   * });\n   *\n   * @example\n   * // With redacted LLM content\n   * session.injectAssistantMessage({\n   *   content: 'Full product details for the user...',\n   *   llmContent: '[Product details summary]'\n   * });\n   */\n  public injectAssistantMessage(\n    options: InjectAssistantMessageOptions\n  ): AgentWidgetMessage {\n    return this.injectMessage({ ...options, role: \"assistant\" });\n  }\n\n  /**\n   * Convenience method for injecting user messages.\n   * Role defaults to 'user'.\n   *\n   * @example\n   * session.injectUserMessage({\n   *   content: 'Add iPhone 15 Pro to my cart'\n   * });\n   */\n  public injectUserMessage(\n    options: InjectUserMessageOptions\n  ): AgentWidgetMessage {\n    return this.injectMessage({ ...options, role: \"user\" });\n  }\n\n  /**\n   * Convenience method for injecting system messages.\n   * Role defaults to 'system'.\n   *\n   * @example\n   * // Inject context that guides LLM behavior\n   * session.injectSystemMessage({\n   *   content: '[Context updated]',  // Minimal display\n   *   llmContent: 'User is viewing iPhone 15 Pro. Cart has 2 items.'\n   * });\n   */\n  public injectSystemMessage(\n    options: InjectSystemMessageOptions\n  ): AgentWidgetMessage {\n    return this.injectMessage({ ...options, role: \"system\" });\n  }\n\n  /**\n   * Inject multiple messages in a single batch with one sort and one render pass.\n   */\n  public injectMessageBatch(optionsList: InjectMessageOptions[]): AgentWidgetMessage[] {\n    const results: AgentWidgetMessage[] = [];\n\n    for (const options of optionsList) {\n      const {\n        role,\n        content,\n        llmContent,\n        contentParts,\n        id,\n        createdAt,\n        sequence,\n        streaming = false,\n        voiceProcessing,\n        rawContent\n      } = options;\n\n      const messageId =\n        id ??\n        (role === \"user\"\n          ? generateUserMessageId()\n          : role === \"assistant\"\n            ? generateAssistantMessageId()\n            : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`);\n\n      const message: AgentWidgetMessage = {\n        id: messageId,\n        role,\n        content,\n        createdAt: createdAt ?? new Date().toISOString(),\n        sequence: sequence ?? this.nextSequence(),\n        streaming,\n        ...(llmContent !== undefined && { llmContent }),\n        ...(contentParts !== undefined && { contentParts }),\n        ...(voiceProcessing !== undefined && { voiceProcessing }),\n        ...(rawContent !== undefined && { rawContent })\n      };\n\n      results.push(message);\n    }\n\n    // Add all messages, sort once, notify once\n    this.messages = this.sortMessages([...this.messages, ...results]);\n    this.callbacks.onMessagesChanged([...this.messages]);\n\n    return results;\n  }\n\n  /**\n   * Convenience method for injecting a registered component directive as\n   * an assistant message: the same shape Persona produces from a streamed\n   * `{ \"text\": \"...\", \"component\": \"...\", \"props\": {...} }` payload.\n   *\n   * Sets `content` to `text`, `rawContent` to the JSON directive (so\n   * `extractComponentDirectiveFromMessage` can find it), and forwards\n   * `llmContent` / `id` / `createdAt` / `sequence`.\n   *\n   * @example\n   * session.injectComponentDirective({\n   *   component: \"DynamicForm\",\n   *   props: { title: \"Book a demo\", fields: [...] },\n   *   text: \"Share your details to book a demo.\",\n   *   llmContent: \"[Showed booking form]\"\n   * });\n   */\n  public injectComponentDirective(\n    options: InjectComponentDirectiveOptions\n  ): AgentWidgetMessage {\n    const {\n      component,\n      props = {},\n      text = \"\",\n      llmContent,\n      id,\n      createdAt,\n      sequence\n    } = options;\n\n    const directive: { text: string; component: string; props: Record<string, unknown> } = {\n      text,\n      component,\n      props\n    };\n\n    return this.injectMessage({\n      role: \"assistant\",\n      content: text,\n      rawContent: JSON.stringify(directive),\n      ...(llmContent !== undefined && { llmContent }),\n      ...(id !== undefined && { id }),\n      ...(createdAt !== undefined && { createdAt }),\n      ...(sequence !== undefined && { sequence })\n    });\n  }\n\n  public async sendMessage(\n    rawInput: string,\n    options?: {\n      viaVoice?: boolean;\n      /** Multi-modal content parts (e.g., images) to include with the message */\n      contentParts?: ContentPart[];\n    }\n  ) {\n    const input = rawInput.trim();\n    // Allow sending if there's text OR attachments\n    if (!input && (!options?.contentParts || options.contentParts.length === 0)) return;\n\n    this.stopSpeaking();\n    this.abortController?.abort();\n    // A new user turn supersedes any in-flight WebMCP resolve from the prior\n    // turn. Tear them down here (they own controllers separate from the shared\n    // one) so a lingering resolve can't race the new dispatch or post a stale\n    // /resume against a superseded execution.\n    this.abortWebMcpResolves();\n    // A new turn also supersedes any pending durable reconnect from the prior\n    // turn (cancels backoff/listeners, clears the old resume handle).\n    this.teardownReconnect();\n\n    // Generate IDs for both user message and expected assistant response\n    const userMessageId = generateUserMessageId();\n    const assistantMessageId = generateAssistantMessageId();\n    // The active assistant bubble for a durable reconnect is captured from the\n    // real streamed message events (see handleEvent), not pre-assigned here:\n    // the proxy path auto-generates a different id than `assistantMessageId`.\n    this.activeAssistantMessageId = null;\n\n    const userMessage: AgentWidgetMessage = {\n      id: userMessageId,\n      role: \"user\",\n      content: input || IMAGE_ONLY_MESSAGE_FALLBACK_TEXT, // Display text (fallback if only images)\n      createdAt: new Date().toISOString(),\n      sequence: this.nextSequence(),\n      viaVoice: options?.viaVoice || false,\n      // Include contentParts if provided (for multi-modal messages)\n      ...(options?.contentParts && options.contentParts.length > 0 && {\n        contentParts: options.contentParts\n      })\n    };\n\n    this.appendMessage(userMessage);\n    this.setStreaming(true);\n\n    const controller = new AbortController();\n    this.abortController = controller;\n\n    const snapshot = [...this.messages];\n\n    try {\n      await this.client.dispatch(\n        {\n          messages: snapshot,\n          signal: controller.signal,\n          assistantMessageId // Pass expected assistant message ID for tracking\n        },\n        this.handleEvent\n      );\n    } catch (error) {\n      // A durable drop fired the dispatch wrapper's `finally` (plain `idle`)\n      // first, which already flipped us into `resuming` and armed reconnect.\n      // The subsequent dispatch rejection must NOT paint a dispatch-error\n      // bubble: the turn is being resumed, not failed.\n      if (this.status === \"resuming\" || this.reconnecting) return;\n      // Check if this is an abort error (user canceled, navigated away, etc.)\n      // In these cases, don't show fallback - the request was intentionally interrupted\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === 'AbortError' ||\n         error.message.includes('aborted') ||\n         error.message.includes('abort'));\n\n      if (!isAbortError) {\n        const content = buildDispatchErrorContent(\n          error,\n          this.config.errorMessage\n        );\n        // An override that returns \"\" suppresses the fallback bubble entirely\n        // (onError still fires below).\n        if (content) {\n          const fallback: AgentWidgetMessage = {\n            id: assistantMessageId, // Use the pre-generated ID for fallback too\n            role: \"assistant\",\n            createdAt: new Date().toISOString(),\n            content,\n            sequence: this.nextSequence()\n          };\n\n          this.appendMessage(fallback);\n        }\n      }\n\n      this.setStatus(\"idle\");\n      this.setStreaming(false);\n      this.abortController = null;\n\n      if (!isAbortError) {\n        if (error instanceof Error) {\n          this.callbacks.onError?.(error);\n        } else {\n          this.callbacks.onError?.(new Error(String(error)));\n        }\n      }\n    }\n  }\n\n  /**\n   * Continue the conversation without adding a new user message.\n   * Triggers the model to respond based on the current conversation state.\n   *\n   * Use this for automatic continuation after action handlers inject data\n   * (e.g., search results) that the model should analyze.\n   *\n   * @example\n   * // After injecting search results, trigger model to analyze them\n   * session.injectAssistantMessage({ content: 'Found 5 products...' });\n   * session.continueConversation();\n   */\n  public async continueConversation() {\n    // Don't continue if already streaming\n    if (this.streaming) return;\n\n    this.abortController?.abort();\n    this.teardownReconnect();\n\n    const assistantMessageId = generateAssistantMessageId();\n    this.activeAssistantMessageId = null;\n\n    this.setStreaming(true);\n\n    const controller = new AbortController();\n    this.abortController = controller;\n\n    const snapshot = [...this.messages];\n\n    try {\n      await this.client.dispatch(\n        {\n          messages: snapshot,\n          signal: controller.signal,\n          assistantMessageId\n        },\n        this.handleEvent\n      );\n    } catch (error) {\n      if (this.status === \"resuming\" || this.reconnecting) return;\n      // Check if this is an abort error (a prior in-flight stream was canceled,\n      // the user navigated away, etc.). In these cases, don't show fallback or\n      // fire onError - the request was intentionally interrupted.\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === 'AbortError' ||\n         error.message.includes('aborted') ||\n         error.message.includes('abort'));\n\n      if (!isAbortError) {\n        const content = buildDispatchErrorContent(\n          error,\n          this.config.errorMessage\n        );\n        // An override that returns \"\" suppresses the fallback bubble entirely\n        // (onError still fires below).\n        if (content) {\n          const fallback: AgentWidgetMessage = {\n            id: assistantMessageId,\n            role: \"assistant\",\n            createdAt: new Date().toISOString(),\n            content,\n            sequence: this.nextSequence()\n          };\n\n          this.appendMessage(fallback);\n        }\n      }\n      this.setStatus(\"idle\");\n      this.setStreaming(false);\n      this.abortController = null;\n      if (!isAbortError) {\n        if (error instanceof Error) {\n          this.callbacks.onError?.(error);\n        } else {\n          this.callbacks.onError?.(new Error(String(error)));\n        }\n      }\n    }\n  }\n\n  /**\n   * Connect an external SSE stream (e.g. from an approval endpoint) and\n   * process it through the SDK's native event pipeline.\n   */\n  public async connectStream(\n    stream: ReadableStream<Uint8Array>,\n    options?: {\n      assistantMessageId?: string;\n      allowReentry?: boolean;\n      /**\n       * Durable reconnect: keep the assistant message identified by\n       * `assistantMessageId` OPEN so the replayed deltas keep filling it. The\n       * default stale-finalize pass would otherwise seal the very bubble we're\n       * resuming into.\n       */\n      preserveAssistantId?: boolean;\n      /**\n       * Durable reconnect: text already shown in the resumed bubble, so the\n       * fresh stream's accumulator continues from it (the replay carries only\n       * post-cursor deltas, not the full text).\n       */\n      seedContent?: string;\n    }\n  ): Promise<void> {\n    if (this.streaming && !options?.allowReentry) return;\n    if (!options?.allowReentry) {\n      this.abortController?.abort();\n    }\n\n    // Durable reconnect keeps filling the same bubble: track its id so the\n    // cursor handler attaches to it.\n    if (options?.preserveAssistantId && options.assistantMessageId) {\n      this.activeAssistantMessageId = options.assistantMessageId;\n    }\n\n    // Finalize any stale streaming messages from the previous stream\n    // (e.g., tool messages interrupted by approval pause), except the bubble\n    // an active reconnect is resuming into.\n    const preserveId = options?.preserveAssistantId\n      ? options.assistantMessageId\n      : undefined;\n    let hasStale = false;\n    for (const msg of this.messages) {\n      if (msg.streaming && msg.id !== preserveId) {\n        msg.streaming = false;\n        hasStale = true;\n      }\n    }\n    if (hasStale) {\n      this.callbacks.onMessagesChanged([...this.messages]);\n    }\n\n    this.setStreaming(true);\n\n    try {\n      await this.client.processStream(\n        stream,\n        this.handleEvent,\n        options?.assistantMessageId,\n        options?.seedContent\n      );\n    } catch (error) {\n      // During a durable reconnect a thrown resume stream is just another drop:\n      // the reconnect loop owns the retry/backoff. Don't paint an error or tear\n      // down, stay in `resuming`.\n      if (this.status === \"resuming\" || this.reconnecting) return;\n      this.setStatus(\"error\");\n      // Mirror the idle/error handlers: a failed resume stream must not tear\n      // down streaming/abortController while another WebMCP resolve is still\n      // confirming or executing. The in-flight resolve's `finally` owns the\n      // teardown once `webMcpResolveControllers` drains.\n      if (this.webMcpResolveControllers.size === 0) {\n        this.setStreaming(false);\n        this.abortController = null;\n      }\n      this.callbacks.onError?.(\n        error instanceof Error ? error : new Error(String(error))\n      );\n    }\n  }\n\n  /**\n   * Install the native approval-bubble confirm handler on the WebMCP bridge\n   * when the integrator hasn't supplied a custom `webmcp.onConfirm`. Without\n   * this, the bridge falls back to a blunt `window.confirm`. Safe to call\n   * repeatedly (e.g. after the client is re-created in `updateConfig`).\n   */\n  private wireDefaultWebMcpConfirm(): void {\n    const webmcp = this.config.webmcp;\n    if (webmcp?.enabled === true && !webmcp.onConfirm) {\n      this.client.setWebMcpConfirmHandler((info) =>\n        this.requestWebMcpApproval(info)\n      );\n    }\n  }\n\n  /**\n   * Default WebMCP confirm gate: render Persona's native in-panel approval\n   * bubble and resolve when the user clicks Approve/Deny. Returns immediately\n   * with `true` when `webmcp.autoApprove(info)` opts the tool out of the gate\n   * (e.g. a read-only catalog search), so no bubble is shown. The bridge\n   * awaits this Promise before executing the page tool.\n   */\n  public requestWebMcpApproval(info: WebMcpConfirmInfo): Promise<boolean> {\n    // Per-tool policy hook: auto-allow opted-out tools without any UI. A\n    // throwing predicate must not block the call, so fall through to an\n    // explicit gate on error.\n    try {\n      if (this.config.webmcp?.autoApprove?.(info) === true) {\n        return Promise.resolve(true);\n      }\n    } catch {\n      // fall through to explicit approval\n    }\n\n    const approval: AgentWidgetApproval = {\n      id: `webmcp-${++this.webMcpApprovalSeq}`,\n      status: \"pending\",\n      agentId: \"\",\n      executionId: \"\",\n      toolName: info.toolName,\n      toolType: \"webmcp\",\n      description:\n        info.description ?? `Allow the assistant to run ${info.toolName}?`,\n      parameters: info.args,\n    };\n    const approvalMessageId = `approval-${approval.id}`;\n\n    this.upsertMessage({\n      id: approvalMessageId,\n      role: \"assistant\",\n      content: \"\",\n      createdAt: new Date().toISOString(),\n      streaming: false,\n      variant: \"approval\",\n      approval,\n    });\n\n    return new Promise<boolean>((resolve) => {\n      this.webMcpApprovalResolvers.set(approvalMessageId, resolve);\n    });\n  }\n\n  /**\n   * Resolve a pending WebMCP approval bubble (from the Approve/Deny click in\n   * `ui.ts`). Updates the bubble to its resolved state and unblocks the\n   * bridge Promise parked in `requestWebMcpApproval`. No-op if already\n   * resolved (double-click, re-render).\n   */\n  public resolveWebMcpApproval(\n    approvalMessageId: string,\n    decision: \"approved\" | \"denied\"\n  ): void {\n    const resolve = this.webMcpApprovalResolvers.get(approvalMessageId);\n    if (!resolve) return;\n    this.webMcpApprovalResolvers.delete(approvalMessageId);\n\n    const existing = this.messages.find((m) => m.id === approvalMessageId);\n    if (existing?.approval) {\n      this.upsertMessage({\n        ...existing,\n        approval: {\n          ...existing.approval,\n          status: decision,\n          resolvedAt: Date.now(),\n        },\n      });\n    }\n\n    resolve(decision === \"approved\");\n  }\n\n  /**\n   * Resolve a tool approval request (approve or deny).\n   * Updates the approval message status, calls the API (or custom onDecision),\n   * and pipes the response stream through connectStream().\n   */\n  public async resolveApproval(\n    approval: AgentWidgetApproval,\n    decision: 'approved' | 'denied',\n    options?: AgentWidgetApprovalDecisionOptions\n  ): Promise<void> {\n    // 1. Update approval message status immediately for responsive UI\n    const approvalMessageId = `approval-${approval.id}`;\n    const updatedApproval: AgentWidgetApproval = {\n      ...approval,\n      status: decision,\n      resolvedAt: Date.now(),\n    };\n    // Anchor the bubble where the agent paused for permission. An approval is a\n    // timeline checkpoint, not a \"now\" event, so resolving it must preserve the\n    // original message's createdAt/sequence: otherwise sortMessages (which\n    // orders by createdAt first) would re-stamp it to now and float it past any\n    // message created later (e.g. a long-pending approval resolved after more\n    // conversation, or restored/replayed transcripts).\n    const existing = this.messages.find((m) => m.id === approvalMessageId);\n    const updatedMessage: AgentWidgetMessage = {\n      id: approvalMessageId,\n      role: \"assistant\",\n      content: \"\",\n      createdAt: existing?.createdAt ?? new Date().toISOString(),\n      ...(existing?.sequence !== undefined ? { sequence: existing.sequence } : {}),\n      streaming: false,\n      variant: \"approval\",\n      approval: updatedApproval,\n    };\n    this.upsertMessage(updatedMessage);\n\n    // Show the standalone typing indicator immediately while we wait for the\n    // approval round-trip. Install an abortController so cancel() works during\n    // the silent gap. See `resolveAskUserQuestion` for the same pattern.\n    this.abortController?.abort();\n    this.abortController = new AbortController();\n    this.setStreaming(true);\n\n    // 2. Call onDecision callback if provided, otherwise use client.resolveApproval()\n    const approvalConfig = this.config.approval;\n    const onDecision = approvalConfig && typeof approvalConfig === 'object' ? approvalConfig.onDecision : undefined;\n\n    try {\n      let response: Response | ReadableStream<Uint8Array> | void;\n\n      if (onDecision) {\n        response = await onDecision(\n          {\n            approvalId: approval.id,\n            executionId: approval.executionId,\n            agentId: approval.agentId,\n            toolName: approval.toolName,\n          },\n          decision,\n          options\n        );\n      } else {\n        response = await this.client.resolveApproval(\n          {\n            agentId: approval.agentId,\n            executionId: approval.executionId,\n            approvalId: approval.id,\n          },\n          decision\n        );\n      }\n\n      // 3. Pipe through connectStream if we got a response with a body\n      if (response) {\n        let stream: ReadableStream<Uint8Array> | null = null;\n        if (response instanceof Response) {\n          if (!response.ok) {\n            const errorData = await response.json().catch(() => null);\n            throw new Error(\n              errorData?.error ?? `Approval request failed: ${response.status}`\n            );\n          }\n          stream = response.body;\n        } else if (response instanceof ReadableStream) {\n          stream = response;\n        }\n\n        if (stream) {\n          await this.connectStream(stream, { allowReentry: true });\n        } else {\n          if (decision === 'denied') {\n            // No stream body for denied: inject a denial message\n            this.appendMessage({\n              id: `denial-${approval.id}`,\n              role: \"assistant\",\n              content: \"Tool execution was denied by user.\",\n              createdAt: new Date().toISOString(),\n              streaming: false,\n              sequence: this.nextSequence(),\n            });\n          }\n          // No body to pipe: drop the pre-set streaming flag so the indicator\n          // doesn't linger forever.\n          this.setStreaming(false);\n          this.abortController = null;\n        }\n      } else {\n        // onDecision returned void / no response: drop the pre-set flag.\n        this.setStreaming(false);\n        this.abortController = null;\n      }\n    } catch (error) {\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === 'AbortError' ||\n         error.message.includes('aborted') ||\n         error.message.includes('abort'));\n\n      this.setStreaming(false);\n      this.abortController = null;\n\n      if (!isAbortError) {\n        this.callbacks.onError?.(\n          error instanceof Error ? error : new Error(String(error))\n        );\n      }\n    }\n  }\n\n  /**\n   * Resolve a paused `ask_user_question` LOCAL tool call.\n   *\n   * When the server emits `step_await` for `ask_user_question`, the widget\n   * renders the answer-pill sheet and calls this method once the user\n   * picks. Steps:\n   *   1. POST the answer to `/resume` via `client.resumeFlow`.\n   *   2. Pipe the resulting SSE stream through `connectStream()` so the\n   *      paused agent execution continues.\n   *   3. Append a user-visible bubble with the answer text so the\n   *      transcript reads naturally.\n   */\n  /**\n   * Persist in-progress answers and the current page index for a multi-question\n   * `ask_user_question` payload, so a refresh resumes on the same page with\n   * prior answers intact. Called by ui.ts on every Back/Next/pick interaction.\n   */\n  public persistAskUserQuestionProgress(\n    toolMessage: AgentWidgetMessage,\n    progress: {\n      answers: Record<string, string | string[]>;\n      currentIndex: number;\n    }\n  ): void {\n    const current = this.messages.find((m) => m.id === toolMessage.id);\n    if (!current) return;\n    this.upsertMessage({\n      ...current,\n      agentMetadata: {\n        ...current.agentMetadata,\n        askUserQuestionAnswers: progress.answers,\n        askUserQuestionIndex: progress.currentIndex,\n      },\n    });\n  }\n\n  /**\n   * Flip an `ask_user_question` tool message from awaiting → answered so\n   * render passes stop re-mounting its answer-pill sheet. Idempotent.\n   * When `answers` is provided, persists the full structured answer Record\n   * atomically with the answered flag: guarding against later events that\n   * could re-emit the tool message and clobber the per-pick persisted\n   * answers via top-level merge.\n   */\n  public markAskUserQuestionResolved(\n    toolMessage: AgentWidgetMessage,\n    answers?: Record<string, string | string[]>\n  ): void {\n    const current = this.messages.find((m) => m.id === toolMessage.id);\n    if (!current) return;\n    this.upsertMessage({\n      ...current,\n      agentMetadata: {\n        ...current.agentMetadata,\n        awaitingLocalTool: false,\n        askUserQuestionAnswered: true,\n        ...(answers ? { askUserQuestionAnswers: answers } : {}),\n      },\n    });\n  }\n\n  public async resolveAskUserQuestion(\n    toolMessage: AgentWidgetMessage,\n    answer: string | Record<string, string | string[]>\n  ): Promise<void> {\n    // Idempotent: guards against rapid double-clicks on answer pills before\n    // the re-render swaps the card to its collapsed/answered state.\n    const live = this.messages.find((m) => m.id === toolMessage.id);\n    if (live?.agentMetadata?.askUserQuestionAnswered === true) return;\n\n    const executionId = toolMessage.agentMetadata?.executionId;\n    const toolName = toolMessage.toolCall?.name;\n    if (!executionId || !toolName) {\n      this.callbacks.onError?.(\n        new Error(\n          \"resolveAskUserQuestion: message is missing executionId or toolCall.name\"\n        )\n      );\n      return;\n    }\n\n    // Flip answered flag first so the next render skips the sheet re-mount,\n    // avoiding the race between removeAskUserQuestionSheet's 180ms slide-out\n    // timer and the renders that fire as the resume stream lands. Pass the\n    // structured answer Record (when present) so it's atomically persisted\n    // alongside the flag: the answered-state review card depends on\n    // `agentMetadata.askUserQuestionAnswers` being populated at render time.\n    //\n    // For single-question payloads, callers (built-in pick handler, plugins)\n    // resolve with a plain string. Derive a `{ [questionText]: answer }` Record\n    // from the toolCall args so the answered-card render path is consistent\n    // with grouped flows.\n    let structuredAnswers: Record<string, string | string[]> | undefined =\n      typeof answer === \"string\" ? undefined : answer;\n    if (structuredAnswers === undefined && typeof answer === \"string\") {\n      const args = toolMessage.toolCall?.args as\n        | { questions?: Array<{ question?: unknown }> }\n        | undefined;\n      const questions = Array.isArray(args?.questions) ? args!.questions : [];\n      if (questions.length === 1) {\n        const qText = typeof questions[0]?.question === \"string\"\n          ? (questions[0].question as string)\n          : \"\";\n        if (qText) structuredAnswers = { [qText]: answer };\n      }\n    }\n    this.markAskUserQuestionResolved(toolMessage, structuredAnswers);\n\n    // Show the standalone typing indicator immediately: the network round-trip\n    // to /resume is otherwise silent, which reads as broken. The render\n    // condition in ui.ts already shows the indicator once streaming flips true\n    // and the last message is a user bubble (the answer we inject below).\n    // Install an abortController so cancel() works during this silent gap.\n    this.abortController?.abort();\n    this.abortController = new AbortController();\n    this.setStreaming(true);\n\n    // Inject Q→A pair messages: one assistant bubble per question, one user\n    // bubble per answer, so the transcript reads like a normal conversation.\n    // The original ask_user_question tool message is suppressed by the\n    // renderer once `askUserQuestionAnswered` is true. Skipped questions get\n    // a muted italic `*Skipped*` user bubble (rendered through the standard\n    // markdown pipeline).\n    const toolCallId = toolMessage.toolCall!.id;\n    const args = toolMessage.toolCall?.args as\n      | { questions?: Array<{ question?: unknown; header?: unknown }> }\n      | undefined;\n    const questions = Array.isArray(args?.questions) ? args!.questions : [];\n    if (questions.length === 0) {\n      const fallback =\n        typeof answer === \"string\"\n          ? answer\n          : Object.entries(answer)\n              .map(\n                ([q, v]) => `${q}: ${Array.isArray(v) ? v.join(\", \") : v}`\n              )\n              .join(\" | \");\n      this.appendMessage({\n        id: `ask-user-answer-${toolCallId}`,\n        role: \"user\",\n        content: fallback,\n        createdAt: new Date().toISOString(),\n        streaming: false,\n        sequence: this.nextSequence(),\n      });\n    } else {\n      const stored = structuredAnswers ?? {};\n      questions.forEach((p, i) => {\n        const qText = typeof p?.question === \"string\" ? p.question : \"\";\n        if (!qText) return;\n        const ans = stored[qText];\n        const answerStr = Array.isArray(ans)\n          ? ans.join(\", \")\n          : typeof ans === \"string\"\n            ? ans\n            : \"\";\n        this.appendMessage({\n          id: `ask-user-q-${toolCallId}-${i}`,\n          role: \"assistant\",\n          content: qText,\n          createdAt: new Date().toISOString(),\n          streaming: false,\n          sequence: this.nextSequence(),\n        });\n        this.appendMessage({\n          id: `ask-user-a-${toolCallId}-${i}`,\n          role: \"user\",\n          content: answerStr || \"*Skipped*\",\n          createdAt: new Date().toISOString(),\n          streaming: false,\n          sequence: this.nextSequence(),\n        });\n      });\n    }\n\n    try {\n      const response = await this.client.resumeFlow(executionId, {\n        [toolName]: answer,\n      });\n\n      if (!response.ok) {\n        const errorData = await response.json().catch(() => null);\n        throw new Error(\n          errorData?.error ?? `Resume failed: ${response.status}`\n        );\n      }\n\n      if (response.body) {\n        await this.connectStream(response.body, { allowReentry: true });\n      } else {\n        // No body to pipe: drop the pre-set streaming flag so the indicator\n        // doesn't linger forever.\n        this.setStreaming(false);\n        this.abortController = null;\n      }\n    } catch (error) {\n      // Mirror sendMessage: a cancel() during the await aborts the controller\n      // and surfaces an AbortError: don't treat that as a real failure.\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === 'AbortError' ||\n         error.message.includes('aborted') ||\n         error.message.includes('abort'));\n\n      this.setStreaming(false);\n      this.abortController = null;\n\n      if (!isAbortError) {\n        this.callbacks.onError?.(\n          error instanceof Error ? error : new Error(String(error))\n        );\n      }\n    }\n  }\n\n  /**\n   * Collect an auto-resolving LOCAL-tool `step_await` (`webmcp:*` page tools\n   * and the built-in `suggest_replies`) into a per-executionId batch\n   * and schedule a single deferred flush. Parallel calls (core#3878) emit\n   * several `step_await`s for ONE paused execution within the same stream tick;\n   * buffering them and flushing once lets us post ONE `/resume` keyed by the\n   * per-call `webMcpToolCallId` rather than racing N name-keyed resumes on the\n   * same execution (which 404'd on the second and hung the turn).\n   *\n   * Deferred via `queueMicrotask` (epoch-guarded) for the same reason the old\n   * direct resolve was: handleEvent must return first so the dispatch's\n   * `connectStream` sees end-of-stream and releases the shared abortController\n   * before a resolve grabs it.\n   *\n   * Awaits without an `executionId` or `toolCall.id` can't be batched (no key)\n   *: route them straight to the single-call path, which surfaces the malformed\n   * wire shape via `onError` / an `isError` resume.\n   */\n  private enqueueWebMcpAwait(toolMessage: AgentWidgetMessage): void {\n    const executionId = toolMessage.agentMetadata?.executionId;\n    const callId = toolMessage.toolCall?.id;\n    if (!executionId || !callId) {\n      const queuedEpoch = this.webMcpEpoch;\n      queueMicrotask(() => {\n        if (queuedEpoch !== this.webMcpEpoch) return;\n        void this.resolveWebMcpToolCall(toolMessage);\n      });\n      return;\n    }\n\n    let batch = this.webMcpAwaitBatches.get(executionId);\n    if (!batch) {\n      batch = { snapshots: [], seen: new Set() };\n      this.webMcpAwaitBatches.set(executionId, batch);\n    }\n    // Duplicate step_await re-emit for a call already in this batch: ignore.\n    if (batch.seen.has(callId)) return;\n    batch.seen.add(callId);\n    batch.snapshots.push(toolMessage);\n    // NB: no flush is scheduled here. Flushing happens once the stream that is\n    // delivering these awaits ENDS (handleEvent's `status: idle` →\n    // scheduleWebMcpBatchFlush). Flushing per-await on the next microtask would\n    // race SSE chunk boundaries: two PARALLEL step_awaits split across separate\n    // `read()` chunks would flush the first alone and post a partial resume.\n    // Waiting for stream end guarantees every parallel await is collected first.\n  }\n\n  /**\n   * Flush every buffered local-tool await batch, one `/resume` per executionId.\n   * Called once a stream ends (`status: idle` / `error`): by then all parallel\n   * `step_await`s the stream carried have been collected, even if split across\n   * SSE chunks. Deferred via `queueMicrotask` (epoch-guarded) so the idle\n   * handler returns first and the stream's end-of-stream teardown (streaming /\n   * abortController) settles before a resolve grabs them: the same ordering the\n   * single-call resolve always relied on.\n   */\n  private scheduleWebMcpBatchFlush(): void {\n    if (this.webMcpAwaitBatches.size === 0) return;\n    const queuedEpoch = this.webMcpEpoch;\n    queueMicrotask(() => {\n      if (queuedEpoch !== this.webMcpEpoch) return;\n      for (const executionId of [...this.webMcpAwaitBatches.keys()]) {\n        this.flushWebMcpAwaitBatch(executionId);\n      }\n    });\n  }\n\n  /**\n   * Run a buffered batch of local-tool awaits for one executionId. Size 1\n   * (single call, or distinct-tool turns that happened to arrive alone) takes\n   * the original single-call path; size >1 (parallel calls) takes the batched\n   * path that posts ONE `/resume`. The batch is removed from the map up front\n   * so any later sibling re-emit (e.g. from a re-pause) forms a fresh batch\n   * rather than mutating one already in flight.\n   */\n  private flushWebMcpAwaitBatch(executionId: string): void {\n    const batch = this.webMcpAwaitBatches.get(executionId);\n    if (!batch) return;\n    this.webMcpAwaitBatches.delete(executionId);\n    const { snapshots } = batch;\n    if (snapshots.length === 1) {\n      void this.resolveWebMcpToolCall(snapshots[0]);\n    } else if (snapshots.length > 1) {\n      void this.resolveWebMcpToolCallBatch(executionId, snapshots);\n    }\n  }\n\n  private resolveWebMcpToolStartedAt(\n    toolMessage: AgentWidgetMessage,\n  ): number {\n    const stored = this.messages.find((m) => m.id === toolMessage.id);\n    const candidates = [\n      stored?.toolCall?.startedAt,\n      toolMessage.toolCall?.startedAt,\n    ];\n    for (const candidate of candidates) {\n      if (typeof candidate === \"number\" && Number.isFinite(candidate)) {\n        return candidate;\n      }\n    }\n    return Date.now();\n  }\n\n  /**\n   * Persisted-resolution guard for `suggest_replies`. The in-memory dedupe\n   * sets (`webMcpInflightKeys` / `webMcpResolvedKeys`) are cleared by\n   * hydrateMessages/clearMessages/cancel, but `suggestRepliesResolved`\n   * survives on the stored message, so a stale `step_await` re-emit after a\n   * hydration must not re-POST `/resume` for an already-resolved call (the\n   * historical double-resume failure mode the batching work exists to avoid).\n   * Checks the LIVE message first; the handleEvent snapshot is a fresh wire\n   * skeleton whose metadata never carries the flag.\n   */\n  private isSuggestRepliesAlreadyResolved(\n    toolMessage: AgentWidgetMessage,\n  ): boolean {\n    if (toolMessage.toolCall?.name !== SUGGEST_REPLIES_TOOL_NAME) return false;\n    const stored = this.messages.find((m) => m.id === toolMessage.id);\n    return (\n      (stored ?? toolMessage).agentMetadata?.suggestRepliesResolved === true\n    );\n  }\n\n  private markWebMcpToolRunning(\n    toolMessage: AgentWidgetMessage,\n  ): number {\n    const startedAt = this.resolveWebMcpToolStartedAt(toolMessage);\n    this.upsertMessage({\n      ...toolMessage,\n      streaming: true,\n      agentMetadata: {\n        ...toolMessage.agentMetadata,\n        awaitingLocalTool: false,\n      },\n      toolCall: toolMessage.toolCall\n        ? {\n            ...toolMessage.toolCall,\n            status: \"running\",\n            startedAt,\n            completedAt: undefined,\n            duration: undefined,\n            durationMs: undefined,\n          }\n        : toolMessage.toolCall,\n    });\n    return startedAt;\n  }\n\n  private markWebMcpToolComplete(\n    toolMessage: AgentWidgetMessage,\n    result: unknown,\n    startedAt: number,\n    completedAt = Date.now(),\n    extraMetadata?: Partial<\n      NonNullable<AgentWidgetMessage[\"agentMetadata\"]>\n    >,\n  ): void {\n    // A teardown such as clearMessages()/hydrateMessages()/new send can remove\n    // the bubble while an aborted WebMCP promise is settling. Never resurrect a\n    // cleared message just to mark the old resolve complete.\n    if (!this.messages.some((message) => message.id === toolMessage.id)) return;\n    this.upsertMessage({\n      ...toolMessage,\n      streaming: false,\n      agentMetadata: {\n        ...toolMessage.agentMetadata,\n        awaitingLocalTool: false,\n        ...extraMetadata,\n      },\n      toolCall: toolMessage.toolCall\n        ? {\n            ...toolMessage.toolCall,\n            status: \"complete\",\n            result,\n            startedAt,\n            completedAt,\n            duration: undefined,\n            durationMs: Math.max(0, completedAt - startedAt),\n          }\n        : toolMessage.toolCall,\n    });\n  }\n\n  /**\n   * Resolve TWO OR MORE parallel local-tool awaits sharing one paused\n   * executionId with a SINGLE `/resume` (core#3878). Each call is executed\n   * against the page registry concurrently: every gated call renders its own\n   * native approval bubble, and a sibling's confirm Promise never blocks\n   * another's execution. Outputs are keyed by per-call `webMcpToolCallId`\n   * (server prefers it over tool name; name-keying remains the fallback for\n   * legacy single/distinct-tool turns), so two calls to the SAME tool no longer\n   * collide. The server is tolerant: any call we omit (declined-after-abort,\n   * dedupe, exec failure) simply re-pauses and is retried on its re-emit.\n   *\n   * Mirrors `resolveWebMcpToolCall`'s dedupe / abort / streaming machinery, but\n   * shares one resume POST and marks every resolved key on that POST's HTTP OK.\n   */\n  private async resolveWebMcpToolCallBatch(\n    executionId: string,\n    snapshots: AgentWidgetMessage[],\n  ): Promise<void> {\n    type ExecutedWebMcpTool = {\n      dedupeKey: string;\n      resumeKey: string;\n      output: unknown;\n      toolMessage: AgentWidgetMessage;\n      startedAt: number;\n      completedAt: number;\n    };\n    const claimedKeys: string[] = [];\n    const controllers: AbortController[] = [];\n    // Dedicated controller for the shared resume fetch so cancel() can abort it\n    // alongside the per-call ones (all live in webMcpResolveControllers).\n    const resumeController = new AbortController();\n    this.webMcpResolveControllers.add(resumeController);\n    this.setStreaming(true);\n\n    // Phase 1: execute every pending call concurrently. A null result means\n    // the call was deduped, aborted, or threw; it's omitted from the resume and\n    // (per the tolerant server) re-pauses for retry.\n    const executed = await Promise.all(\n      snapshots.map(async (toolMessage) => {\n        const wireToolName = toolMessage.toolCall?.name;\n        const callId = toolMessage.toolCall?.id;\n        if (!wireToolName || !callId) return null;\n\n        const dedupeKey = `${executionId}:${callId}`;\n        if (\n          this.webMcpInflightKeys.has(dedupeKey) ||\n          this.webMcpResolvedKeys.has(dedupeKey) ||\n          this.isSuggestRepliesAlreadyResolved(toolMessage)\n        ) {\n          return null;\n        }\n        this.webMcpInflightKeys.add(dedupeKey);\n        claimedKeys.push(dedupeKey);\n\n        // Clear the awaiting flag and keep the tool bubble running while the\n        // browser-side WebMCP promise is in flight. The initial `step_await`\n        // only means the server paused for a local tool; it is not completion.\n        const startedAt = this.markWebMcpToolRunning(toolMessage);\n\n        // Per-call id wins for resume keying; fall back to the wire tool name\n        // for legacy servers that don't emit `webMcpToolCallId`.\n        const resumeKey =\n          toolMessage.agentMetadata?.webMcpToolCallId ?? wireToolName;\n\n        // Built-in fire-and-forget tool: no bridge, no confirm gate, no\n        // browser-side execution: the chips render from the message list and\n        // the canned output joins the batch's single /resume.\n        if (wireToolName === SUGGEST_REPLIES_TOOL_NAME) {\n          return {\n            dedupeKey,\n            resumeKey,\n            output: suggestRepliesToolResult(),\n            toolMessage,\n            startedAt,\n            completedAt: Date.now(),\n          };\n        }\n\n        const controller = new AbortController();\n        this.webMcpResolveControllers.add(controller);\n        controllers.push(controller);\n\n        const execPromise = this.client.executeWebMcpToolCall(\n          wireToolName,\n          toolMessage.toolCall?.args,\n          controller.signal,\n        );\n\n        let output: unknown;\n        if (!execPromise) {\n          output = {\n            isError: true,\n            content: [\n              { type: \"text\", text: \"WebMCP not enabled on this widget.\" },\n            ],\n          };\n        } else {\n          try {\n            output = await execPromise;\n          } catch (error) {\n            const isAbortError =\n              error instanceof Error &&\n              (error.name === \"AbortError\" ||\n                error.message.includes(\"aborted\") ||\n                error.message.includes(\"abort\"));\n            if (!isAbortError) {\n              this.callbacks.onError?.(\n                error instanceof Error ? error : new Error(String(error)),\n              );\n            }\n            this.markWebMcpToolComplete(\n              toolMessage,\n              buildWebMcpErrorResult(\n                isAbortError\n                  ? \"Aborted by cancel()\"\n                  : getWebMcpErrorMessage(error),\n              ),\n              startedAt,\n            );\n            // Release the dedupe claim so a re-emit can retry this call.\n            this.webMcpInflightKeys.delete(dedupeKey);\n            return null;\n          }\n        }\n        if (controller.signal.aborted) {\n          this.markWebMcpToolComplete(\n            toolMessage,\n            buildWebMcpErrorResult(\"Aborted by cancel()\"),\n            startedAt,\n          );\n          this.webMcpInflightKeys.delete(dedupeKey);\n          return null;\n        }\n        return {\n          dedupeKey,\n          resumeKey,\n          output,\n          toolMessage,\n          startedAt,\n          completedAt: Date.now(),\n        };\n      }),\n    );\n\n    let ready: ExecutedWebMcpTool[] = [];\n    try {\n      ready = executed.filter((r): r is ExecutedWebMcpTool => r !== null);\n      // Everything deduped/aborted/failed: nothing to post.\n      if (ready.length === 0) return;\n\n      const toolOutputs: Record<string, unknown> = {};\n      for (const r of ready) {\n        // Two omitted-on-collision safety: if two calls somehow resolve to the\n        // same key (only possible on a legacy name fallback), last write wins:        // the server re-pauses the unrepresented call for retry.\n        toolOutputs[r.resumeKey] = r.output;\n      }\n\n      const response = await this.client.resumeFlow(executionId, toolOutputs, {\n        signal: resumeController.signal,\n      });\n      if (!response.ok) {\n        const errorData = await response.json().catch(() => null);\n        throw new Error(errorData?.error ?? `Resume failed: ${response.status}`);\n      }\n      // Server accepted the batch: mark every included call resolved so stale\n      // re-emits don't re-execute the page tool, then complete each bubble.\n      // Do this only after /resume HTTP success; if /resume fails, the server\n      // may still be paused and the retry path must not show a final result.\n      for (const r of ready) {\n        this.webMcpResolvedKeys.add(r.dedupeKey);\n        this.markWebMcpToolComplete(\n          r.toolMessage,\n          r.output,\n          r.startedAt,\n          r.completedAt,\n          r.toolMessage.toolCall?.name === SUGGEST_REPLIES_TOOL_NAME\n            ? { suggestRepliesResolved: true }\n            : undefined,\n        );\n      }\n      if (response.body) {\n        await this.connectStream(response.body, { allowReentry: true });\n      }\n    } catch (error) {\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === \"AbortError\" ||\n          error.message.includes(\"aborted\") ||\n          error.message.includes(\"abort\"));\n      if (!isAbortError) {\n        this.callbacks.onError?.(\n          error instanceof Error ? error : new Error(String(error)),\n        );\n      } else {\n        for (const r of ready) {\n          this.markWebMcpToolComplete(\n            r.toolMessage,\n            buildWebMcpErrorResult(\"Aborted by cancel()\"),\n            r.startedAt,\n          );\n        }\n      }\n    } finally {\n      for (const key of claimedKeys) {\n        this.webMcpInflightKeys.delete(key);\n      }\n      for (const controller of controllers) {\n        this.webMcpResolveControllers.delete(controller);\n      }\n      this.webMcpResolveControllers.delete(resumeController);\n      if (this.webMcpResolveControllers.size === 0 && !this.abortController) {\n        this.setStreaming(false);\n      }\n    }\n  }\n\n  /**\n   * Resolve a paused auto-resolving LOCAL tool call and post the result to\n   * `/resume`: `webmcp:*` calls execute against the host page's tool\n   * registry; the built-in `suggest_replies` skips execution entirely and\n   * resumes with a canned \"shown\" result (the chips render from the message\n   * list, not from this resolve).\n   *\n   * Triggered automatically from `handleEvent` when a `step_await`-derived\n   * message arrives for such a tool: the user does not click a pill; the\n   * bridge's confirm-bubble gate (WebMCP only) is the only interactive\n   * surface.\n   *\n   * Idempotent on the message's `toolCall.id`: re-emits of the same step_await\n   * (e.g. from message coalescing) won't double-fire `tool.execute`. Failure\n   * modes, declined, timed out, throw, unknown tool, all resolve into a\n   * `{ isError: true, content: [...] }` payload that resumes the dispatch\n   * cleanly so the agent can recover.\n   */\n  public async resolveWebMcpToolCall(\n    toolMessage: AgentWidgetMessage,\n  ): Promise<void> {\n    const executionId = toolMessage.agentMetadata?.executionId;\n    const wireToolName = toolMessage.toolCall?.name;\n    const toolCallId = toolMessage.toolCall?.id;\n\n    // Malformed step_await wire shapes shouldn't silently strand the\n    // server-side dispatch. Three failure modes:\n    //   - no executionId: no /resume target exists; surface to the host\n    //     via onError so an operator can react. This is a server-side\n    //     wire-shape bug: Persona can't recover it from the client.\n    //   - no wireToolName: defensive guard: handleEvent only calls us\n    //     for an auto-resolving local tool name (`webmcp:*` or\n    //     `suggest_replies`), so this path indicates a direct caller\n    //     misuse. Silent return.\n    //   - no toolCallId: dedupe key falls apart, but the server can still\n    //     advance if we post an isError for the wireToolName. Do that\n    //     and bail before the dedupe path.\n    if (!executionId) {\n      this.callbacks.onError?.(\n        new Error(\n          \"WebMCP step_await missing executionId: dispatch left paused.\",\n        ),\n      );\n      return;\n    }\n    if (!wireToolName) return;\n    if (!toolCallId) {\n      // No toolCall.id → no per-call dedupe key. Fall back to a synthetic\n      // `(executionId):(wireToolName)` so identical malformed re-emits don't\n      // re-POST /resume. Idempotent on duplicate bad payloads.\n      const malformedKey = `${executionId}:__no_tool_id__:${wireToolName}`;\n      if (\n        this.webMcpInflightKeys.has(malformedKey) ||\n        this.webMcpResolvedKeys.has(malformedKey)\n      ) {\n        return;\n      }\n      this.webMcpInflightKeys.add(malformedKey);\n      try {\n        await this.resumeWithToolOutput(executionId, wireToolName, {\n          isError: true,\n          content: [\n            {\n              type: \"text\",\n              text: \"WebMCP step_await missing toolCall.id: cannot execute the page tool.\",\n            },\n          ],\n        });\n        this.webMcpResolvedKeys.add(malformedKey);\n      } catch (error) {\n        this.callbacks.onError?.(\n          error instanceof Error ? error : new Error(String(error)),\n        );\n      } finally {\n        this.webMcpInflightKeys.delete(malformedKey);\n      }\n      return;\n    }\n\n    // Dedupe key scoped by executionId: see `webMcpInflightKeys` doc comment\n    // for the failure-recovery + cross-dispatch rationale. The persisted\n    // `suggestRepliesResolved` guard backs the in-memory sets across\n    // hydrations.\n    const dedupeKey = `${executionId}:${toolCallId}`;\n    if (\n      this.webMcpInflightKeys.has(dedupeKey) ||\n      this.webMcpResolvedKeys.has(dedupeKey) ||\n      this.isSuggestRepliesAlreadyResolved(toolMessage)\n    ) {\n      return;\n    }\n    this.webMcpInflightKeys.add(dedupeKey);\n\n    // Mark resolved on the message so the UI's local-tool sheet (if any\n    // generic one ever lands) does not show: this is a fully-automatic\n    // tool from the user's perspective, modulo the confirm bubble. Keep the\n    // tool bubble running until the browser-side promise resolves; the\n    // initial step_await was only the server pause, not tool completion.\n    const startedAt = this.markWebMcpToolRunning(toolMessage);\n\n    // Per-resolve AbortController, NOT the shared `this.abortController`.\n    // A single turn can produce multiple `webmcp:*` step_await messages:    // both PARALLEL (two awaits in one stream) and, more commonly, CHAINED\n    // (tool A → /resume → tool B, where B's step_await arrives inside A's\n    // resume SSE stream). The old code pre-aborted `this.abortController`\n    // here to mirror the sibling resolve paths; in the chained case that\n    // aborted the stream still delivering B, so B never executed and its\n    // /resume was never POSTed: the dispatch hung forever. Using a dedicated\n    // per-resolve controller leaves the in-flight resume stream untouched.\n    // cancel()/clearMessages()/hydrateMessages()/sendMessage() iterate\n    // `webMcpResolveControllers` to tear these down on a real stop / new turn.\n    const resolveController = new AbortController();\n    this.webMcpResolveControllers.add(resolveController);\n    const { signal } = resolveController;\n    this.setStreaming(true);\n\n    // Built-in fire-and-forget tool: no bridge, no confirm gate, no\n    // browser-side execution: the chips render from the message list and the\n    // canned output resumes the execution immediately. Branch BEFORE any\n    // bridge access so the missing-bridge error path can never fire for it.\n    const isSuggestReplies = wireToolName === SUGGEST_REPLIES_TOOL_NAME;\n\n    const args = toolMessage.toolCall?.args;\n    // Thread the signal INTO the bridge: short-circuits the confirm bubble\n    // and the execute() race on cancel(), so a late confirm-approval after\n    // cancel() cannot fire a host-page side effect with no matching /resume.\n    const execPromise = isSuggestReplies\n      ? null\n      : this.client.executeWebMcpToolCall(wireToolName, args, signal);\n\n    let phase: \"execute\" | \"resume\" = \"execute\";\n    let completedAt = startedAt;\n    try {\n      let resumeOutput: unknown;\n      if (isSuggestReplies) {\n        resumeOutput = suggestRepliesToolResult();\n      } else if (!execPromise) {\n        // Client has no bridge (config.webmcp.enabled !== true). Resume with\n        // an error so the dispatch can advance instead of hanging.\n        resumeOutput = {\n          isError: true,\n          content: [\n            { type: \"text\", text: \"WebMCP not enabled on this widget.\" },\n          ],\n        };\n      } else {\n        resumeOutput = await execPromise;\n      }\n      completedAt = Date.now();\n      // If cancel() fired during execute, the bridge returned an aborted\n      // result: don't post it. The server's SSE has been torn down; a\n      // /resume now would just produce an orphan dispatch on the server.\n      // Streaming/teardown is handled by the shared `finally` below (gated on\n      // the resolve set) so we don't clobber a sibling resolve or a live\n      // dispatch's controller here.\n      if (signal.aborted) {\n        this.markWebMcpToolComplete(\n          toolMessage,\n          buildWebMcpErrorResult(\"Aborted by cancel()\"),\n          startedAt,\n        );\n        return;\n      }\n      // Mark resolved as soon as the HTTP /resume returns OK: not after the\n      // SSE stream finishes. `connectStream` swallows downstream SSE errors\n      // (they surface via onError, not by rethrowing), so awaiting it doesn't\n      // tell us whether the server actually processed the resume. Marking\n      // here pairs with the dedupe semantics: a successful POST means the\n      // server got the answer; later step_await re-emits for the same\n      // toolCall.id are stale and must not re-execute the page tool.\n      // Key the resume by the per-call id (core#3878) when present; the server\n      // prefers it over tool name. Falls back to the wire tool name for legacy\n      // servers: the original name-keyed contract, still correct for a single\n      // call (only same-tool PARALLEL calls could collide on the name).\n      const resumeKey =\n        toolMessage.agentMetadata?.webMcpToolCallId ?? wireToolName;\n      phase = \"resume\";\n      await this.resumeWithToolOutput(executionId, resumeKey, resumeOutput, {\n        onHttpOk: () => {\n          this.webMcpResolvedKeys.add(dedupeKey);\n          this.markWebMcpToolComplete(\n            toolMessage,\n            resumeOutput,\n            startedAt,\n            completedAt,\n            isSuggestReplies ? { suggestRepliesResolved: true } : undefined,\n          );\n        },\n        signal,\n      });\n    } catch (error) {\n      const isAbortError =\n        error instanceof Error &&\n        (error.name === \"AbortError\" ||\n          error.message.includes(\"aborted\") ||\n          error.message.includes(\"abort\"));\n      // Streaming/teardown handled by the shared `finally` (gated on the\n      // resolve set): do NOT null the shared `this.abortController` here; it\n      // may belong to a live dispatch or sibling resolve, not to us.\n      if (phase === \"execute\" || isAbortError || signal.aborted) {\n        this.markWebMcpToolComplete(\n          toolMessage,\n          buildWebMcpErrorResult(\n            isAbortError || signal.aborted\n              ? \"Aborted by cancel()\"\n              : getWebMcpErrorMessage(error),\n          ),\n          startedAt,\n        );\n      }\n      if (!isAbortError) {\n        // The bridge normalizes tool errors into result objects, so reaching\n        // here means a network failure during `/resume` itself, OR a stream\n        // hookup error. Surface to onError, but DO NOT mark resolved: a\n        // later step_await re-emit should be allowed to retry the resume.\n        this.callbacks.onError?.(\n          error instanceof Error ? error : new Error(String(error)),\n        );\n      }\n    } finally {\n      this.webMcpInflightKeys.delete(dedupeKey);\n      this.webMcpResolveControllers.delete(resolveController);\n      // Only flip streaming off when this was the last in-flight resolve AND\n      // no shared dispatch is live. Otherwise a finishing resolve would hide\n      // the typing indicator while a sibling (parallel) or successor (chained)\n      // resolve, or a live dispatch, is still running.\n      if (this.webMcpResolveControllers.size === 0 && !this.abortController) {\n        this.setStreaming(false);\n      }\n    }\n  }\n\n  /**\n   * POST `/resume` with a SINGLE tool's output and pipe the resulting SSE\n   * stream back through `connectStream`. Shared by every single-call local-tool\n   * resolve path (ask_user_question and single WebMCP calls). Parallel WebMCP\n   * calls use `resolveWebMcpToolCallBatch`, which posts one resume for many.\n   *\n   * `resumeKey` is the `toolOutputs` map key: the per-call `webMcpToolCallId`\n   * for WebMCP (core#3878), or the tool name for ask_user_question / legacy\n   * servers. `onHttpOk` runs synchronously between the HTTP-status check and the\n   * stream pipe; it lets the WebMCP resolve path commit the dedupe flag at\n   * \"server accepted the answer\" rather than \"stream finished cleanly\".\n   */\n  private async resumeWithToolOutput(\n    executionId: string,\n    resumeKey: string,\n    output: unknown,\n    options?: { onHttpOk?: () => void; signal?: AbortSignal },\n  ): Promise<void> {\n    const response = await this.client.resumeFlow(\n      executionId,\n      { [resumeKey]: output },\n      { signal: options?.signal },\n    );\n    if (!response.ok) {\n      const errorData = await response.json().catch(() => null);\n      throw new Error(errorData?.error ?? `Resume failed: ${response.status}`);\n    }\n    options?.onHttpOk?.();\n    if (response.body) {\n      await this.connectStream(response.body, { allowReentry: true });\n    } else if (this.webMcpResolveControllers.size === 0) {\n      // No stream to pipe. Clear streaming only when no WebMCP resolve is in\n      // flight: for a WebMCP caller the current resolve's controller is still\n      // in the set, so its own `finally` (gated on the set draining) owns the\n      // teardown. Non-WebMCP callers (ask_user_question) keep the old behavior.\n      this.setStreaming(false);\n      this.abortController = null;\n    }\n  }\n\n  /**\n   * Tear down every in-flight WebMCP resolve and advance the epoch. Each\n   * resolve owns a dedicated AbortController (chained/parallel resolves don't\n   * share one), so we abort them individually; the aborts propagate into the\n   * bridge's execute race and into each `/resume` fetch signal. Bumping\n   * `webMcpEpoch` strands any resolve still deferred in a queued microtask:   * it captured the prior epoch and bails before installing a fresh\n   * controller, so it can't escape this teardown. Called from every stop /\n   * new-turn boundary (cancel, clearMessages, hydrateMessages, sendMessage).\n   */\n  private abortWebMcpResolves(): void {\n    for (const controller of this.webMcpResolveControllers) {\n      controller.abort();\n    }\n    this.webMcpResolveControllers.clear();\n    // Settle every approval bubble still awaiting a click. The bridge parks a\n    // resolve on `await requestConfirm(...)` (→ requestWebMcpApproval) and only\n    // re-checks `signal.aborted` AFTER that await returns: aborting the\n    // controller above does NOT unblock it. Left unsettled, the bridge's\n    // execute(), its `/resume`, and the resolve's `finally` would all hang\n    // forever (and the resolver map would leak across teardowns). Route through\n    // `resolveWebMcpApproval(…, \"denied\")` so each parked Promise resolves\n    // `false` AND its bubble message flips out of `pending` (no stale \"Approve/\n    // Deny\" left clickable). The bridge then returns cleanly and its\n    // post-confirm `signal.aborted` guard bails before any host-page side effect\n    // or stale `/resume`. Snapshot the keys first: resolveWebMcpApproval\n    // mutates the map as it deletes each resolver.\n    for (const approvalMessageId of [...this.webMcpApprovalResolvers.keys()]) {\n      this.resolveWebMcpApproval(approvalMessageId, \"denied\");\n    }\n    // Drop any awaits buffered for a not-yet-flushed batch: their messages are\n    // being torn down, and a microtask-deferred flush must not survive. The\n    // epoch bump below also strands an already-scheduled flush.\n    this.webMcpAwaitBatches.clear();\n    this.webMcpEpoch++;\n  }\n\n  public cancel() {\n    this.abortController?.abort();\n    this.abortController = null;\n    // A user stop also cancels any pending/in-flight durable reconnect and\n    // clears the resume handle (the abort above already killed its fetch).\n    this.teardownReconnect();\n    // Tear down every in-flight WebMCP resolve (each owns its own controller,\n    // independent of the shared one above). Clear the inflight set so retries\n    // are possible if the user re-issues the same step_await context.\n    this.abortWebMcpResolves();\n    this.webMcpInflightKeys.clear();\n    // Stop any in-progress audio too: when the user hits \"stop\", they want\n    // the assistant to actually stop talking, not just stop generating tokens.\n    // Both helpers are safe no-ops when audio isn't configured.\n    this.stopSpeaking();\n    this.stopVoicePlayback();\n    this.setStreaming(false);\n    this.setStatus(\"idle\");\n  }\n\n  public clearMessages() {\n    this.stopSpeaking();\n    this.abortController?.abort();\n    this.abortController = null;\n    this.teardownReconnect();\n    // Tear down every in-flight WebMCP resolve too: their messages are about\n    // to be wiped, and a microtask-deferred resolve must not survive the clear.\n    this.abortWebMcpResolves();\n    this.messages = [];\n    this.agentExecution = null;\n    this.clearArtifactState();\n    // Clearing messages also wipes the WebMCP dedupe state: a fresh\n    // conversation should not refuse to call a webmcp:* tool just because\n    // a tool with the same key resolved in the prior conversation.\n    this.webMcpInflightKeys.clear();\n    this.webMcpResolvedKeys.clear();\n    // A fresh conversation must resend the full WebMCP tool list on its next\n    // turn: drop the diff-only fingerprint cache (server keys by recordId, so\n    // a new conversation has no stored set to match).\n    this.client.resetClientToolsFingerprint();\n    this.setStreaming(false);\n    this.setStatus(\"idle\");\n    this.callbacks.onMessagesChanged([...this.messages]);\n  }\n\n  public getArtifacts(): PersonaArtifactRecord[] {\n    return [...this.artifacts.values()];\n  }\n\n  public getArtifactById(id: string): PersonaArtifactRecord | undefined {\n    return this.artifacts.get(id);\n  }\n\n  public getSelectedArtifactId(): string | null {\n    return this.selectedArtifactId;\n  }\n\n  public selectArtifact(id: string | null): void {\n    this.selectedArtifactId = id;\n    this.emitArtifactsState();\n  }\n\n  public clearArtifacts(): void {\n    this.clearArtifactState();\n  }\n\n  public upsertArtifact(manual: PersonaArtifactManualUpsert): PersonaArtifactRecord {\n    const id =\n      manual.id ||\n      `art_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;\n    const rec: PersonaArtifactRecord =\n      manual.artifactType === \"markdown\"\n        ? {\n            id,\n            artifactType: \"markdown\",\n            title: manual.title,\n            status: \"complete\",\n            markdown: manual.content,\n            ...(manual.file ? { file: manual.file } : {})\n          }\n        : {\n            id,\n            artifactType: \"component\",\n            title: manual.title,\n            status: \"complete\",\n            component: manual.component,\n            props: manual.props ?? {}\n          };\n    this.artifacts.set(id, rec);\n    this.selectedArtifactId = id;\n    this.emitArtifactsState();\n    if (manual.transcript !== false) {\n      this.injectArtifactRefBlock(rec);\n    }\n    return rec;\n  }\n\n  /**\n   * Injects (or refreshes) the in-thread artifact block for a programmatically\n   * upserted artifact, matching the streamed UX: the resolved display mode\n   * picks the component (\"card\"/\"panel\" → reference card, \"inline\" → inline\n   * preview).\n   *\n   * Unlike the streamed path (which embeds content on `artifact_complete`),\n   * the programmatic record is complete up front, so the final markdown /\n   * file meta / component name + props are embedded immediately and the block\n   * hydrates after a refresh without the original registry state.\n   *\n   * A re-upsert of the same id rebuilds the existing block's persisted\n   * rawContent in place (the registry is not persisted, so hydration reads\n   * these props); without this the block would keep the first version's\n   * content after a refresh.\n   */\n  private injectArtifactRefBlock(rec: PersonaArtifactRecord): void {\n    const refId = `artifact-ref-${rec.id}`;\n    const displayMode = resolveArtifactDisplayMode(\n      this.config.features?.artifacts,\n      rec.artifactType\n    );\n    const rawContent = buildArtifactRefRawContent(displayMode, {\n      artifactId: rec.id,\n      title: rec.title,\n      artifactType: rec.artifactType,\n      status: \"complete\",\n      ...(rec.file ? { file: rec.file } : {}),\n      ...(rec.component ? { component: rec.component } : {}),\n      ...(rec.props ? { componentProps: rec.props } : {}),\n      ...(rec.markdown !== undefined ? { markdown: rec.markdown } : {})\n    });\n    // Re-upsert: mutate the STORED message (inject stores a copy via\n    // ensureSequence, so a local copy would be lost) and notify so the\n    // transcript re-renders and persistence picks up the new version.\n    const existing = this.messages.find((m) => m.id === refId);\n    if (existing) {\n      existing.rawContent = rawContent;\n      existing.streaming = false;\n      this.callbacks.onMessagesChanged([...this.messages]);\n      return;\n    }\n    this.injectAssistantMessage({\n      id: refId,\n      content: \"\",\n      rawContent\n    });\n  }\n\n  private clearArtifactState(): void {\n    if (this.artifacts.size === 0 && this.selectedArtifactId === null) return;\n    this.artifacts.clear();\n    this.selectedArtifactId = null;\n    this.emitArtifactsState();\n  }\n\n  private emitArtifactsState(): void {\n    this.callbacks.onArtifactsState?.({\n      artifacts: [...this.artifacts.values()],\n      selectedId: this.selectedArtifactId\n    });\n  }\n\n  private applyArtifactStreamEvent(ev: AgentWidgetEvent): void {\n    switch (ev.type) {\n      case \"artifact_start\": {\n        if (ev.artifactType === \"markdown\") {\n          this.artifacts.set(ev.id, {\n            id: ev.id,\n            artifactType: \"markdown\",\n            title: ev.title,\n            status: \"streaming\",\n            markdown: \"\",\n            ...(ev.file ? { file: ev.file } : {})\n          });\n        } else {\n          this.artifacts.set(ev.id, {\n            id: ev.id,\n            artifactType: \"component\",\n            title: ev.title,\n            status: \"streaming\",\n            component: ev.component ?? \"\",\n            props: {}\n          });\n        }\n        this.selectedArtifactId = ev.id;\n        break;\n      }\n      case \"artifact_delta\": {\n        const row = this.artifacts.get(ev.id);\n        if (row?.artifactType === \"markdown\") {\n          row.markdown = (row.markdown ?? \"\") + ev.artDelta;\n        }\n        break;\n      }\n      case \"artifact_update\": {\n        const row = this.artifacts.get(ev.id);\n        if (row?.artifactType === \"component\") {\n          row.props = { ...row.props, ...ev.props };\n          if (ev.component) row.component = ev.component;\n        }\n        break;\n      }\n      case \"artifact_complete\": {\n        const row = this.artifacts.get(ev.id);\n        if (row) row.status = \"complete\";\n        break;\n      }\n      default:\n        return;\n    }\n    this.emitArtifactsState();\n  }\n\n  public hydrateMessages(messages: AgentWidgetMessage[]) {\n    this.abortController?.abort();\n    this.abortController = null;\n    // Hydration replaces the conversation: also cancel any pending reconnect and\n    // clear the resume handle (a boot resume re-arms via resumeFromHandle after).\n    this.teardownReconnect();\n    // Hydration replaces the conversation: abort and forget every in-flight\n    // WebMCP resolve; their messages are about to be replaced.\n    this.abortWebMcpResolves();\n    // Wipe the WebMCP dedupe state alongside the message restore: the\n    // incoming snapshot is treated as a fresh conversation context.\n    this.webMcpInflightKeys.clear();\n    this.webMcpResolvedKeys.clear();\n    this.messages = this.sortMessages(\n      messages.map((message) => ({\n        ...message,\n        streaming: false,\n        sequence: message.sequence ?? this.nextSequence()\n      }))\n    );\n    this.setStreaming(false);\n    this.setStatus(\"idle\");\n    this.callbacks.onMessagesChanged([...this.messages]);\n  }\n\n  public hydrateArtifacts(\n    artifacts: PersonaArtifactRecord[],\n    selectedId: string | null = null\n  ) {\n    this.artifacts.clear();\n    for (const rec of artifacts) {\n      this.artifacts.set(rec.id, { ...rec, status: \"complete\" });\n    }\n    this.selectedArtifactId = selectedId;\n    this.emitArtifactsState();\n  }\n\n  private handleEvent = (event: AgentWidgetEvent) => {\n    if (event.type === \"message\") {\n      this.upsertMessage(event.message);\n\n      // Track the open assistant text bubble's REAL streamed id so a durable\n      // reconnect keeps filling the same message. The proxy path auto-generates\n      // this id (it isn't the session's pre-generated one), so we must read it\n      // off the actual message events, not assume it.\n      if (\n        event.message.role === \"assistant\" &&\n        !event.message.variant &&\n        event.message.streaming\n      ) {\n        this.activeAssistantMessageId = event.message.id;\n      }\n\n      // Local-tool auto-resolve: when a step_await emits a tool-variant\n      // message for a `webmcp:*` tool, or the built-in fire-and-forget\n      // `suggest_replies`: resolve it and post the result to /resume.\n      // Unlike ask_user_question, no user pill click is required; for WebMCP\n      // the bridge's confirm bubble is the only interactive surface, and\n      // suggest_replies resumes with a canned \"shown\" result while the chips\n      // render above the composer.\n      //\n      // Defer via `queueMicrotask` so handleEvent returns FIRST. The current\n      // SSE consumer is still mid-loop; once we return, the dispatch's\n      // `connectStream` sees end-of-stream (server closes the SSE at\n      // step_await), flips status to \"idle\", and clears `abortController`\n      // before our resolve grabs them. Without this, the original dispatch's\n      // finalizer would clobber the new abort controller and `streaming=true`\n      // set inside `resolveWebMcpToolCall`.\n      //\n      // ALWAYS resolve when the wire name carries the `webmcp:` prefix, even\n      // if the bridge is non-operational. Otherwise the dispatch stays paused\n      // indefinitely: `resolveWebMcpToolCall` translates the missing-bridge\n      // case into an isError result that resumes the flow cleanly.\n      // `suggest_replies`, by contrast, is gated on its feature flag: when\n      // `features.suggestReplies.enabled === false` the widget neither\n      // renders chips nor resumes: the same parked-execution posture as a\n      // server-declared ask_user_question with its sheet disabled.\n      const tc = event.message.toolCall;\n      const autoResolvable =\n        !!tc?.name &&\n        (isWebMcpToolName(tc.name) ||\n          (tc.name === SUGGEST_REPLIES_TOOL_NAME &&\n            this.config.features?.suggestReplies?.enabled !== false));\n      if (\n        event.message.agentMetadata?.awaitingLocalTool === true &&\n        autoResolvable\n      ) {\n        // Collect the await into its executionId's batch instead of resolving\n        // it on the spot. Parallel same-tool calls (core#3878) arrive as\n        // separate `step_await`s in the same stream; batching lets us post ONE\n        // `/resume` keyed by per-call id (see `enqueueWebMcpAwait`).\n        this.enqueueWebMcpAwait(event.message);\n      }\n\n      // Track agent execution state from message metadata\n      if (event.message.agentMetadata?.executionId) {\n        if (!this.agentExecution) {\n          this.agentExecution = {\n            executionId: event.message.agentMetadata.executionId,\n            agentId: '',\n            agentName: event.message.agentMetadata.agentName ?? '',\n            status: 'running',\n            currentIteration: event.message.agentMetadata.iteration ?? 0,\n            maxTurns: 0\n          };\n        } else if (event.message.agentMetadata.iteration !== undefined) {\n          this.agentExecution.currentIteration = event.message.agentMetadata.iteration;\n        }\n      }\n    } else if (event.type === \"cursor\") {\n      // Durable-reconnect cursor. Track the highest SSE `id:` seq as the resume\n      // cursor and (lazily) form the resume handle once the executionId is\n      // known. Only durable, resumable executions emit these, so this is the\n      // natural gate: no `id:` lines → no handle → reconnect never arms.\n      this.trackCursor(event.id);\n    } else if (event.type === \"status\") {\n      // A plain `idle` (no `terminal`) on the durable lane, while the run is\n      // genuinely mid-turn, is a dropped connection, not a finish and not an\n      // intentional `step_await` pause. Reconnect instead of finalizing.\n      if (event.status === \"idle\" && !event.terminal && this.isDurableDrop()) {\n        this.beginReconnect();\n        return;\n      }\n      this.setStatus(event.status);\n      if (event.status === \"connecting\") {\n        this.setStreaming(true);\n      } else if (event.status === \"idle\" || event.status === \"error\") {\n        // Graceful terminal or terminal error: the durable turn is over, so\n        // drop the resume handle (no reconnect should arm after this).\n        this.clearResumable();\n        // Keep the typing indicator up while a WebMCP resolve is still in\n        // flight: in a chained turn the intermediate resume stream ends with an\n        // idle status, but the successor tool is still executing. The resolve's\n        // own `finally` flips streaming off once the resolve set drains.\n        if (this.webMcpResolveControllers.size === 0) {\n          this.setStreaming(false);\n          this.abortController = null;\n        }\n        // Mark agent execution as complete when streaming ends: UNLESS local\n        // tools are still outstanding. A batched WebMCP resume is deferred to\n        // the microtask below (so `webMcpResolveControllers` is still empty\n        // here) and a chained resolve may be mid-flight; marking the run\n        // 'complete' now would make isAgentRunning() report a finished run while\n        // page tools are still executing. Stay 'running': the resume stream's\n        // own idle (with batches drained and resolves settled) marks it done.\n        const webMcpPending =\n          this.webMcpAwaitBatches.size > 0 ||\n          this.webMcpResolveControllers.size > 0;\n        if (this.agentExecution?.status === 'running') {\n          if (event.status === \"error\") {\n            this.agentExecution.status = 'error';\n          } else if (!webMcpPending) {\n            this.agentExecution.status = 'complete';\n          }\n        }\n        // The stream that delivered any local-tool `step_await`s has now ended,\n        // so every parallel await it carried is collected. Flush them as ONE\n        // batched `/resume` per executionId (deferred: see\n        // scheduleWebMcpBatchFlush). Runs AFTER the teardown above so a resolve\n        // doesn't fight the end-of-stream streaming/abortController reset.\n        this.scheduleWebMcpBatchFlush();\n      }\n    } else if (event.type === \"error\") {\n      this.setStatus(\"error\");\n      // Terminal error: drop the resume handle so no reconnect arms.\n      this.clearResumable();\n      // Mirror the idle/status handler: don't tear down streaming while a\n      // WebMCP resolve is still confirming/executing on another stream: an\n      // error on one chained resume stream must not hide the typing indicator\n      // (or null a controller) for a sibling/successor resolve still in flight.\n      // The resolve's own `finally` flips streaming off once the set drains.\n      if (this.webMcpResolveControllers.size === 0) {\n        this.setStreaming(false);\n        this.abortController = null;\n      }\n      if (this.agentExecution?.status === 'running') {\n        this.agentExecution.status = 'error';\n      }\n      this.callbacks.onError?.(event.error);\n    } else if (\n      event.type === \"artifact_start\" ||\n      event.type === \"artifact_delta\" ||\n      event.type === \"artifact_update\" ||\n      event.type === \"artifact_complete\"\n    ) {\n      this.applyArtifactStreamEvent(event);\n    }\n  };\n\n  // ── Durable-session reconnect ──────────────────────────────────\n\n  /**\n   * Record the latest SSE `id:` cursor and (lazily) form/advance the resume\n   * handle. Needs an executionId (lifted into `agentExecution` from the same\n   * frame's metadata, handled before this trailing cursor event) and the active\n   * assistant message id.\n   */\n  private trackCursor(id: string): void {\n    const executionId = this.agentExecution?.executionId;\n    if (!executionId || !this.activeAssistantMessageId) return;\n    // Only track while the run is live. A graceful terminal sets the execution\n    // to 'complete'/'error' and clears the handle BEFORE its own frame's\n    // trailing cursor fires; without this guard that cursor would re-arm the\n    // handle and the next plain `idle` would look like a spurious drop.\n    if (this.agentExecution?.status !== \"running\") return;\n    const isNew = this.resumable === null;\n    this.resumable = {\n      executionId,\n      lastEventId: id,\n      assistantMessageId: this.activeAssistantMessageId,\n      status: \"running\",\n    };\n    // Fire immediately when the handle first appears (so the host can persist\n    // the executionId promptly); throttle subsequent cursor advances.\n    this.notifyExecutionState(isNew);\n  }\n\n  /**\n   * The drop gate: is this stream-end a dropped connection we should reconnect,\n   * rather than a graceful finish or an intentional `step_await` pause? ALL must\n   * hold (see plan §Design overview keystone 3).\n   */\n  private isDurableDrop(): boolean {\n    return (\n      this.resumable !== null &&\n      typeof this.config.reconnectStream === \"function\" &&\n      this.abortController?.signal.aborted !== true &&\n      this.webMcpResolveControllers.size === 0 &&\n      this.webMcpAwaitBatches.size === 0 &&\n      !this.isAwaitPending()\n    );\n  }\n\n  /**\n   * True when the run is parked awaiting user input (an unanswered\n   * `ask_user_question` / local-tool await, or a pending approval). Those end\n   * the stream with `idle` and no terminal too, but are NOT drops.\n   */\n  private isAwaitPending(): boolean {\n    return this.messages.some((m) => {\n      if (\n        m.agentMetadata?.awaitingLocalTool === true &&\n        m.agentMetadata?.askUserQuestionAnswered !== true\n      ) {\n        return true;\n      }\n      if (m.variant === \"approval\" && m.approval?.status === \"pending\") {\n        return true;\n      }\n      return false;\n    });\n  }\n\n  /**\n   * Enter the reconnect flow. Keeps the assistant bubble open and `streaming`\n   * true, flips to `resuming`, and kicks the bounded backoff loop. Re-entrant\n   * calls (a second drop while reconnecting) are no-ops.\n   */\n  private beginReconnect(): void {\n    if (this.reconnecting) return;\n    if (!this.resumable || typeof this.config.reconnectStream !== \"function\") {\n      return;\n    }\n    // Flip the visible state synchronously — the bubble stays open and a\n    // `resuming` status is observable immediately (e.g. right after\n    // `resumeFromHandle`). The heavy backoff loop is loaded lazily below.\n    this.reconnecting = true;\n    this.callbacks.onReconnect?.({ phase: \"paused\", handle: this.resumable });\n    this.setStreaming(true);\n    this.setStatus(\"resuming\");\n    void this.loadReconnectController().then((controller) => {\n      // The run may have been torn down (new turn / cancel / hydrate) while the\n      // module was loading; only start the loop if we're still reconnecting.\n      if (this.reconnecting && this.resumable) controller.begin();\n    });\n  }\n\n  /**\n   * Lazily import and instantiate the reconnect controller. Cached so repeated\n   * reconnects reuse one controller (and one module load). Deliberately kept off\n   * every hot path (`sendMessage` / `cancel` / `teardownReconnect`) so a widget\n   * that never reconnects never pulls the chunk.\n   */\n  private loadReconnectController(): Promise<ReconnectController> {\n    if (this.reconnectController) {\n      return Promise.resolve(this.reconnectController);\n    }\n    if (!this.reconnectControllerPromise) {\n      this.reconnectControllerPromise = import(\"./session-reconnect\").then(\n        ({ createReconnectController }) => {\n          const controller = createReconnectController(\n            this.buildReconnectHost()\n          );\n          this.reconnectController = controller;\n          return controller;\n        }\n      );\n    }\n    return this.reconnectControllerPromise;\n  }\n\n  /** The narrow surface the lazily-loaded reconnect loop drives. */\n  private buildReconnectHost(): ReconnectHost {\n    const session = this;\n    return {\n      get config() {\n        return session.config;\n      },\n      getResumable: () => session.resumable,\n      clearResumable: () => session.clearResumable(),\n      getStatus: () => session.status,\n      setStatus: (status) => session.setStatus(status),\n      setStreaming: (streaming) => session.setStreaming(streaming),\n      setReconnecting: (value) => {\n        session.reconnecting = value;\n      },\n      setAbortController: (controller) => {\n        session.abortController = controller;\n      },\n      getMessages: () => session.messages,\n      notifyMessagesChanged: () =>\n        session.callbacks.onMessagesChanged([...session.messages]),\n      resumeConnect: (body, assistantMessageId, seedContent) =>\n        session.connectStream(body, {\n          assistantMessageId,\n          allowReentry: true,\n          preserveAssistantId: true,\n          seedContent,\n        }),\n      appendMessage: (message) => session.appendMessage(message),\n      nextSequence: () => session.nextSequence(),\n      emitReconnect: (event) => session.callbacks.onReconnect?.(event),\n      buildErrorContent: (message) =>\n        buildDispatchErrorContent(\n          new Error(message),\n          session.config.errorMessage\n        ),\n      onError: (error) => session.callbacks.onError?.(error),\n    };\n  }\n\n  /** Public manual retry (e.g. a \"Reconnect\" button). */\n  public reconnectNow(): void {\n    if (this.reconnecting) {\n      // Already trying, so just short-circuit the current backoff. (No-op if the\n      // controller is still loading; the first attempt hasn't slept yet.)\n      this.reconnectController?.wake();\n      return;\n    }\n    this.beginReconnect();\n  }\n\n  /**\n   * Resume a durable turn on boot (tab-reload path). Re-opens the trailing\n   * assistant bubble (or mints one), restores the execution/handle, and\n   * reconnects from the persisted cursor. No-op without `reconnectStream`.\n   */\n  public resumeFromHandle(resume: { executionId: string; after: string }): void {\n    if (typeof this.config.reconnectStream !== \"function\") return;\n    if (this.reconnecting) return;\n\n    let assistantId = this.reopenTrailingAssistant();\n    if (!assistantId) {\n      assistantId = generateAssistantMessageId();\n      this.appendMessage({\n        id: assistantId,\n        role: \"assistant\",\n        content: \"\",\n        createdAt: new Date().toISOString(),\n        streaming: true,\n        sequence: this.nextSequence(),\n      });\n    }\n    this.activeAssistantMessageId = assistantId;\n    if (!this.agentExecution) {\n      this.agentExecution = {\n        executionId: resume.executionId,\n        agentId: \"\",\n        agentName: \"\",\n        status: \"running\",\n        currentIteration: 0,\n        maxTurns: 0,\n      };\n    }\n    this.resumable = {\n      executionId: resume.executionId,\n      lastEventId: resume.after,\n      assistantMessageId: assistantId,\n      status: \"running\",\n    };\n    this.beginReconnect();\n  }\n\n  /**\n   * Reopen the trailing assistant bubble (the persisted partial) so replayed\n   * deltas append to it. Returns its id, or null if the last turn doesn't end\n   * in a plain assistant message.\n   */\n  private reopenTrailingAssistant(): string | null {\n    for (let i = this.messages.length - 1; i >= 0; i--) {\n      const m = this.messages[i];\n      if (m.role === \"assistant\" && !m.variant) {\n        m.streaming = true;\n        this.callbacks.onMessagesChanged([...this.messages]);\n        return m.id;\n      }\n      if (m.role === \"user\") break;\n    }\n    return null;\n  }\n\n  /**\n   * Tear down any in-flight/pending reconnect and clear the resume handle. Runs\n   * on every new turn / cancel / hydrate, so it must NOT pull the reconnect\n   * chunk: it only delegates to the controller if one was already created.\n   */\n  private teardownReconnect(): void {\n    this.reconnecting = false;\n    this.reconnectController?.teardown();\n    this.clearResumable();\n  }\n\n  /** Drop the resume handle and notify the host (`onExecutionState(null)`). */\n  private clearResumable(): void {\n    if (this.executionStateTimer) {\n      clearTimeout(this.executionStateTimer);\n      this.executionStateTimer = null;\n    }\n    const had = this.resumable !== null;\n    this.resumable = null;\n    if (had) this.config.onExecutionState?.(null);\n  }\n\n  /**\n   * Surface the resume handle to the host for persistence. Immediate on\n   * create/clear; trailing-edge throttled on cursor advances so it isn't called\n   * per delta.\n   */\n  private notifyExecutionState(immediate: boolean): void {\n    const cb = this.config.onExecutionState;\n    if (!cb) return;\n    if (immediate) {\n      if (this.executionStateTimer) {\n        clearTimeout(this.executionStateTimer);\n        this.executionStateTimer = null;\n      }\n      cb(this.resumable);\n      return;\n    }\n    if (this.executionStateTimer) return; // a trailing call is already queued\n    this.executionStateTimer = setTimeout(() => {\n      this.executionStateTimer = null;\n      this.config.onExecutionState?.(this.resumable);\n    }, 500);\n  }\n\n  /** The current durable resume handle, if any (read-only). */\n  public getResumableHandle(): ResumableHandle | null {\n    return this.resumable;\n  }\n\n  private setStatus(status: AgentWidgetSessionStatus) {\n    if (this.status === status) return;\n    this.status = status;\n    this.callbacks.onStatusChanged(status);\n  }\n\n  private setStreaming(streaming: boolean) {\n    if (this.streaming === streaming) return;\n    const wasStreaming = this.streaming;\n    this.streaming = streaming;\n    this.callbacks.onStreamingChanged(streaming);\n\n    // Speak the latest assistant message when streaming completes\n    if (wasStreaming && !streaming) {\n      this.speakLatestAssistantMessage();\n    }\n  }\n\n  /**\n   * Speak the latest assistant message using the Web Speech API\n   * if text-to-speech is enabled in the config.\n   */\n  private speakLatestAssistantMessage() {\n    const ttsConfig = this.config.textToSpeech;\n    if (!ttsConfig?.enabled) return;\n\n    // Determine if browser TTS should fire:\n    // - provider 'browser' (or unset): always use browser TTS\n    // - provider 'runtype': only if browserFallback is enabled\n    const useBrowserTts =\n      !ttsConfig.provider ||\n      ttsConfig.provider === 'browser' ||\n      (ttsConfig.provider === 'runtype' && ttsConfig.browserFallback);\n    if (!useBrowserTts) return;\n\n    // Find the last assistant message with actual content\n    const lastAssistant = [...this.messages]\n      .reverse()\n      .find(m => m.role === 'assistant' && m.content && !m.voiceProcessing);\n\n    if (!lastAssistant) return;\n\n    // Skip if already spoken by Runtype provider's audio playback\n    if (this.ttsSpokenMessageIds.has(lastAssistant.id)) {\n      this.ttsSpokenMessageIds.delete(lastAssistant.id);\n      return;\n    }\n\n    const text = resolveSpeakableText(lastAssistant.content);\n    if (!text.trim()) return;\n\n    // Route auto-speak through the same controller as the \"Read aloud\" button\n    // so there's a single owner of the speech engine and the button reflects\n    // playback state (and a single message can't be spoken by two paths at once).\n    void this.readAloud.play(lastAssistant.id, {\n      text,\n      voice: ttsConfig.voice,\n      rate: ttsConfig.rate,\n      pitch: ttsConfig.pitch,\n    });\n  }\n\n  /**\n   * Pick the best available English voice from a list of SpeechSynthesisVoices.\n   * Prefers high-quality remote/natural voices, then enhanced local voices,\n   * then standard local voices. Retained for backwards compatibility; delegates\n   * to the browser speech engine's picker.\n   */\n  static pickBestVoice(voices: SpeechSynthesisVoice[]): SpeechSynthesisVoice {\n    return pickBestVoice(voices);\n  }\n\n  /**\n   * Toggle the per-message \"Read aloud\" action: play → pause → resume (or\n   * play → stop when the engine can't pause). Speaks the assistant message's\n   * text via the configured speech engine (browser Web Speech API by default,\n   * or a hosted engine from `textToSpeech.createEngine`).\n   */\n  public toggleReadAloud(messageId: string): void {\n    const message = this.messages.find(m => m.id === messageId);\n    if (!message || message.role !== 'assistant') return;\n    const text = resolveSpeakableText(message.content || '');\n    if (!text.trim()) return;\n    const tts = this.config.textToSpeech;\n    this.readAloud.toggle(messageId, {\n      text,\n      voice: tts?.voice,\n      rate: tts?.rate,\n      pitch: tts?.pitch,\n    });\n  }\n\n  /** Current read-aloud playback state for a message (`idle` unless active). */\n  public getReadAloudState(messageId: string): ReadAloudState {\n    return this.readAloud.stateFor(messageId);\n  }\n\n  /** Subscribe to read-aloud state changes. Returns an unsubscribe function. */\n  public onReadAloudChange(listener: ReadAloudListener): () => void {\n    return this.readAloud.onChange(listener);\n  }\n\n  /**\n   * Stop any in-progress text-to-speech / read-aloud playback.\n   */\n  public stopSpeaking() {\n    this.readAloud.stop();\n    if (typeof window !== 'undefined' && 'speechSynthesis' in window) {\n      window.speechSynthesis.cancel();\n    }\n  }\n\n  private appendMessage(message: AgentWidgetMessage) {\n    const withSequence = this.ensureSequence(message);\n    this.messages = this.sortMessages([...this.messages, withSequence]);\n    this.callbacks.onMessagesChanged([...this.messages]);\n  }\n\n  private upsertMessage(message: AgentWidgetMessage) {\n    const withSequence = this.ensureSequence(message);\n    const index = this.messages.findIndex((m) => m.id === withSequence.id);\n    if (index === -1) {\n      this.appendMessage(withSequence);\n      return;\n    }\n\n    this.messages = this.messages.map((existing, idx) => {\n      if (idx !== index) return existing;\n      const merged = { ...existing, ...withSequence };\n      // Preserve `ask_user_question` answered state across re-emissions.\n      // Top-level merge would otherwise replace `agentMetadata` wholesale:      // post-resume events (e.g. `tool_complete` re-emitted from a stale\n      // client-side cache) would wipe `askUserQuestionAnswered` and\n      // `askUserQuestionAnswers`, causing the answered review card to\n      // lose its answers and revert to \"(skipped)\" placeholders.\n      if (\n        existing.agentMetadata?.askUserQuestionAnswered === true &&\n        withSequence.agentMetadata\n      ) {\n        merged.agentMetadata = {\n          ...withSequence.agentMetadata,\n          askUserQuestionAnswered: true,\n          ...(existing.agentMetadata.askUserQuestionAnswers\n            ? {\n                askUserQuestionAnswers:\n                  existing.agentMetadata.askUserQuestionAnswers,\n              }\n            : {}),\n          // Keep awaiting flag false once resolved: never let a stale\n          // re-emit flip us back to awaiting.\n          awaitingLocalTool: false,\n        };\n      }\n      // suggest_replies equivalent: preserve the persisted fire-and-forget\n      // resolution across re-emissions. It is the only dedupe signal that\n      // survives a hydration (the in-memory key sets are cleared), so a\n      // stale step_await re-emit must not wipe it before\n      // `isSuggestRepliesAlreadyResolved` checks it in the resolve path.\n      if (\n        existing.agentMetadata?.suggestRepliesResolved === true &&\n        withSequence.agentMetadata\n      ) {\n        merged.agentMetadata = {\n          ...(merged.agentMetadata ?? withSequence.agentMetadata),\n          suggestRepliesResolved: true,\n          awaitingLocalTool: false,\n        };\n      }\n      // Approval equivalent: `approval_complete` carries only the\n      // resolution (approvalId, decision, resolvedBy): the runtime does not\n      // re-send toolName/description/toolType/reason/parameters, so client.ts\n      // rebuilds the approval with empty required fields and the optional\n      // ones absent. A wholesale `approval` replacement would wipe that\n      // context from the resolved bubble on the next full re-render (morph,\n      // virtual-scroll re-mount, storage restore). Merge field-wise instead:\n      // take the resolution from the incoming event, keep existing context\n      // wherever the event is silent or empty.\n      if (\n        existing.approval &&\n        withSequence.approval &&\n        existing.approval.id === withSequence.approval.id\n      ) {\n        const prior = existing.approval;\n        const incoming = withSequence.approval;\n        merged.approval = {\n          ...prior,\n          ...incoming,\n          executionId: incoming.executionId || prior.executionId,\n          toolName: incoming.toolName || prior.toolName,\n          description: incoming.description || prior.description,\n          toolType: incoming.toolType ?? prior.toolType,\n          reason: incoming.reason ?? prior.reason,\n          parameters: incoming.parameters ?? prior.parameters,\n        };\n      }\n      // Auto-resolved local-tool equivalent (`webmcp:*` and the built-in\n      // `suggest_replies`): once such a tool has started resolving (inflight)\n      // or resolved, a duplicate `step_await` re-emit must not flip\n      // `awaitingLocalTool` back to true and resurrect the \"waiting on\n      // local tool\" UI. It also must not overwrite an existing running or\n      // completed toolCall with the fresh running skeleton emitted by client.ts\n      // for every step_await. resolveWebMcpToolCall's dedupe path returns\n      // without re-touching the message, so correct the merge here (also avoids\n      // a one-frame flash before that microtask runs).\n      const reTcName = withSequence.toolCall?.name;\n      const reExecId = withSequence.agentMetadata?.executionId;\n      const reTcId = withSequence.toolCall?.id;\n      if (\n        reTcName &&\n        isAutoResolvedLocalToolName(reTcName) &&\n        reExecId &&\n        reTcId &&\n        withSequence.agentMetadata?.awaitingLocalTool === true\n      ) {\n        const reKey = `${reExecId}:${reTcId}`;\n        const isInflight = this.webMcpInflightKeys.has(reKey);\n        const isResolved = this.webMcpResolvedKeys.has(reKey);\n        const existingToolName = existing.toolCall?.name;\n        const hasCompletedTool =\n          existing.agentMetadata?.executionId === reExecId &&\n          existing.toolCall?.id === reTcId &&\n          existingToolName !== undefined &&\n          isAutoResolvedLocalToolName(existingToolName) &&\n          existing.toolCall?.status === \"complete\";\n        if (isInflight || isResolved || hasCompletedTool) {\n          merged.agentMetadata = {\n            ...(merged.agentMetadata ?? {}),\n            awaitingLocalTool: false,\n          };\n          // Preserve the in-flight/completed tool state. For in-flight calls,\n          // this keeps the original `startedAt`; for completed calls, it keeps\n          // the measured duration/result even when the call completed with a\n          // local/browser error and therefore was not promoted to resolved.\n          merged.toolCall = existing.toolCall;\n          merged.streaming = existing.streaming;\n        }\n      }\n      return merged;\n    });\n    this.messages = this.sortMessages(this.messages);\n    this.callbacks.onMessagesChanged([...this.messages]);\n  }\n\n  private ensureSequence(message: AgentWidgetMessage): AgentWidgetMessage {\n    if (message.sequence !== undefined) {\n      return { ...message };\n    }\n    return {\n      ...message,\n      sequence: this.nextSequence()\n    };\n  }\n\n  private nextSequence() {\n    return this.sequenceCounter++;\n  }\n\n  private sortMessages(messages: AgentWidgetMessage[]) {\n    return [...messages].sort((a, b) => {\n      // Sort by createdAt timestamp first (chronological order)\n      const timeA = new Date(a.createdAt).getTime();\n      const timeB = new Date(b.createdAt).getTime();\n      if (!Number.isNaN(timeA) && !Number.isNaN(timeB) && timeA !== timeB) {\n        return timeA - timeB;\n      }\n\n      // Fall back to sequence if timestamps are equal or invalid\n      const seqA = a.sequence ?? 0;\n      const seqB = b.sequence ?? 0;\n      if (seqA !== seqB) return seqA - seqB;\n\n      // Final fallback to ID\n      return a.id.localeCompare(b.id);\n    });\n  }\n}\n","import type { IconNode } from \"lucide\";\nimport {\n  // ---------- Mandatory (referenced as string literals in widget source) ----------\n  Activity,\n  ArrowDown,\n  ArrowUp,\n  ArrowUpRight,\n  Bot,\n  ChevronDown,\n  ChevronUp,\n  ChevronRight,\n  ChevronLeft,\n  Check,\n  Clipboard,\n  ClipboardCopy,\n  CodeXml,\n  Copy,\n  File as FileIcon,\n  FileCode,\n  FileSpreadsheet,\n  FileText,\n  ImagePlus,\n  Loader,\n  LoaderCircle,\n  Mic,\n  Paperclip,\n  RefreshCw,\n  Search,\n  Send,\n  ShieldAlert,\n  ShieldCheck,\n  ShieldX,\n  Square,\n  ThumbsDown,\n  ThumbsUp,\n  Upload,\n  Volume2,\n  X,\n  // ---------- Forms / inputs ----------\n  User,\n  Mail,\n  Phone,\n  Calendar,\n  Clock,\n  Building,\n  MapPin,\n  Lock,\n  Key,\n  CreditCard,\n  AtSign,\n  Hash,\n  Globe,\n  Link,\n  // ---------- Status / feedback ----------\n  CircleCheck,\n  CircleX,\n  TriangleAlert,\n  Info,\n  Ban,\n  Shield,\n  // ---------- Navigation ----------\n  ArrowLeft,\n  ArrowRight,\n  ExternalLink,\n  Ellipsis,\n  EllipsisVertical,\n  Menu,\n  House,\n  // ---------- Actions ----------\n  Plus,\n  Minus,\n  Pencil,\n  Trash,\n  Trash2,\n  Save,\n  Download,\n  Share,\n  Funnel,\n  Settings,\n  RotateCw,\n  Maximize,\n  Minimize,\n  // ---------- Commerce ----------\n  ShoppingCart,\n  ShoppingBag,\n  Package,\n  Truck,\n  Tag,\n  Gift,\n  Receipt,\n  Wallet,\n  Store,\n  DollarSign,\n  Percent,\n  // ---------- Media ----------\n  Play,\n  Pause,\n  VolumeX,\n  Camera,\n  Image as ImageIcon,\n  Film,\n  Headphones,\n  // ---------- Social / Comms ----------\n  MessageCircle,\n  MessageSquare,\n  Bell,\n  Heart,\n  Star,\n  Eye,\n  EyeOff,\n  Bookmark,\n  // ---------- Time ----------\n  CalendarDays,\n  History,\n  Timer,\n  // ---------- Files ----------\n  Folder,\n  FolderOpen,\n  Files,\n  // ---------- Decorative ----------\n  Sparkles,\n  Zap,\n  Sun,\n  Moon,\n  Flag,\n  // ---------- Devices ----------\n  Monitor,\n  Smartphone,\n} from \"lucide\";\n\n/**\n * Curated registry of lucide icons available to `renderLucideIcon`.\n *\n * The widget used to do `import * as icons from \"lucide\"` and look up\n * icons dynamically by string. That defeated tree-shaking, so the IIFE\n * (CDN/script-tag) bundle shipped all 1640 lucide icons (~400KB of icon\n * data) regardless of which we actually used. This explicit registry\n * lets the bundler drop any icon not listed here.\n *\n * Trade-off: `renderLucideIcon(name)` is now a *closed set*. Names not\n * in this map return `null` and log a warning, exactly as a typo did\n * before. The registry is intentionally generous (~110 icons) so that\n * custom `ComponentRenderer` authors rarely hit a missing-icon dead end.\n *\n * To add icons: add a named import above and a row in `LUCIDE_ICONS`,\n * keyed by the lucide kebab-case name (matches their filename and\n * https://lucide.dev/icons).\n *\n * See `packages/widget/docs/icon-registry-shortlist.md` for the full\n * curation rationale and which icons were considered but excluded.\n */\nconst LUCIDE_ICONS = {\n  // Mandatory\n  \"activity\": Activity,\n  \"arrow-down\": ArrowDown,\n  \"arrow-up\": ArrowUp,\n  \"arrow-up-right\": ArrowUpRight,\n  \"bot\": Bot,\n  \"chevron-down\": ChevronDown,\n  \"chevron-up\": ChevronUp,\n  \"chevron-right\": ChevronRight,\n  \"chevron-left\": ChevronLeft,\n  \"check\": Check,\n  \"clipboard\": Clipboard,\n  \"clipboard-copy\": ClipboardCopy,\n  \"code-xml\": CodeXml,\n  \"copy\": Copy,\n  \"file\": FileIcon,\n  \"file-code\": FileCode,\n  \"file-spreadsheet\": FileSpreadsheet,\n  \"file-text\": FileText,\n  \"image-plus\": ImagePlus,\n  \"loader\": Loader,\n  \"loader-circle\": LoaderCircle,\n  \"mic\": Mic,\n  \"paperclip\": Paperclip,\n  \"refresh-cw\": RefreshCw,\n  \"search\": Search,\n  \"send\": Send,\n  \"shield-alert\": ShieldAlert,\n  \"shield-check\": ShieldCheck,\n  \"shield-x\": ShieldX,\n  \"square\": Square,\n  \"thumbs-down\": ThumbsDown,\n  \"thumbs-up\": ThumbsUp,\n  \"upload\": Upload,\n  \"volume-2\": Volume2,\n  \"x\": X,\n  // Forms / inputs\n  \"user\": User,\n  \"mail\": Mail,\n  \"phone\": Phone,\n  \"calendar\": Calendar,\n  \"clock\": Clock,\n  \"building\": Building,\n  \"map-pin\": MapPin,\n  \"lock\": Lock,\n  \"key\": Key,\n  \"credit-card\": CreditCard,\n  \"at-sign\": AtSign,\n  \"hash\": Hash,\n  \"globe\": Globe,\n  \"link\": Link,\n  // Status / feedback\n  \"circle-check\": CircleCheck,\n  \"circle-x\": CircleX,\n  \"triangle-alert\": TriangleAlert,\n  \"info\": Info,\n  \"ban\": Ban,\n  \"shield\": Shield,\n  // Navigation\n  \"arrow-left\": ArrowLeft,\n  \"arrow-right\": ArrowRight,\n  \"external-link\": ExternalLink,\n  \"ellipsis\": Ellipsis,\n  \"ellipsis-vertical\": EllipsisVertical,\n  \"menu\": Menu,\n  \"house\": House,\n  // Actions\n  \"plus\": Plus,\n  \"minus\": Minus,\n  \"pencil\": Pencil,\n  \"trash\": Trash,\n  \"trash-2\": Trash2,\n  \"save\": Save,\n  \"download\": Download,\n  \"share\": Share,\n  \"funnel\": Funnel,\n  \"settings\": Settings,\n  \"rotate-cw\": RotateCw,\n  \"maximize\": Maximize,\n  \"minimize\": Minimize,\n  // Commerce\n  \"shopping-cart\": ShoppingCart,\n  \"shopping-bag\": ShoppingBag,\n  \"package\": Package,\n  \"truck\": Truck,\n  \"tag\": Tag,\n  \"gift\": Gift,\n  \"receipt\": Receipt,\n  \"wallet\": Wallet,\n  \"store\": Store,\n  \"dollar-sign\": DollarSign,\n  \"percent\": Percent,\n  // Media\n  \"play\": Play,\n  \"pause\": Pause,\n  \"volume-x\": VolumeX,\n  \"camera\": Camera,\n  \"image\": ImageIcon,\n  \"film\": Film,\n  \"headphones\": Headphones,\n  // Social / Comms\n  \"message-circle\": MessageCircle,\n  \"message-square\": MessageSquare,\n  \"bell\": Bell,\n  \"heart\": Heart,\n  \"star\": Star,\n  \"eye\": Eye,\n  \"eye-off\": EyeOff,\n  \"bookmark\": Bookmark,\n  // Time\n  \"calendar-days\": CalendarDays,\n  \"history\": History,\n  \"timer\": Timer,\n  // Files\n  \"folder\": Folder,\n  \"folder-open\": FolderOpen,\n  \"files\": Files,\n  // Decorative\n  \"sparkles\": Sparkles,\n  \"zap\": Zap,\n  \"sun\": Sun,\n  \"moon\": Moon,\n  \"flag\": Flag,\n  // Devices\n  \"monitor\": Monitor,\n  \"smartphone\": Smartphone,\n} as const satisfies Record<string, IconNode>;\n\n/**\n * Names of lucide icons that ship with the widget. Names not in this\n * union return `null` from `renderLucideIcon` (with a console warning).\n */\nexport type IconName = keyof typeof LUCIDE_ICONS;\n\n/**\n * Renders a lucide icon as an inline SVG element. Works inside Shadow\n * DOM and requires no CSS.\n *\n * @param iconName - A lucide kebab-case name from the registry. See\n *   `IconName` for the full list, or `docs/icon-registry-shortlist.md`\n *   for rationale.\n * @param size - The size in pixels (number) or any CSS length string.\n * @param color - Stroke color (default: \"currentColor\").\n * @param strokeWidth - Stroke width (default: 2).\n * @returns SVGElement, or null if the name is not in the registry.\n */\nexport const renderLucideIcon = (\n  iconName: IconName | (string & {}),\n  size: number | string = 24,\n  color: string = \"currentColor\",\n  strokeWidth: number = 2\n): SVGElement | null => {\n  const iconData = (LUCIDE_ICONS as Record<string, IconNode | undefined>)[iconName];\n  if (!iconData) {\n    console.warn(\n      `Lucide icon \"${iconName}\" is not in the Persona registry. ` +\n      `Add it to packages/widget/src/utils/icons.ts (see docs/icon-registry-shortlist.md).`\n    );\n    return null;\n  }\n  return createSvgFromIconData(iconData, size, color, strokeWidth);\n};\n\nfunction createSvgFromIconData(\n  iconData: IconNode,\n  size: number | string,\n  color: string,\n  strokeWidth: number\n): SVGElement | null {\n  if (!Array.isArray(iconData)) return null;\n\n  const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n  svg.setAttribute(\"width\", String(size));\n  svg.setAttribute(\"height\", String(size));\n  svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n  svg.setAttribute(\"fill\", \"none\");\n  svg.setAttribute(\"stroke\", color);\n  svg.setAttribute(\"stroke-width\", String(strokeWidth));\n  svg.setAttribute(\"stroke-linecap\", \"round\");\n  svg.setAttribute(\"stroke-linejoin\", \"round\");\n  svg.setAttribute(\"aria-hidden\", \"true\");\n\n  // IconNode shape: [[\"path\", {\"d\": \"...\"}], [\"circle\", {\"cx\": \"...\"}], ...]\n  iconData.forEach((elementData) => {\n    if (!Array.isArray(elementData) || elementData.length < 2) return;\n    const tagName = elementData[0] as string;\n    const attrs = elementData[1] as Record<string, string> | undefined;\n    if (!attrs) return;\n    const element = document.createElementNS(\"http://www.w3.org/2000/svg\", tagName);\n    Object.entries(attrs).forEach(([key, value]) => {\n      // Skip 'stroke' so the parent SVG's stroke attribute drives color uniformly\n      if (key !== \"stroke\") element.setAttribute(key, String(value));\n    });\n    svg.appendChild(element);\n  });\n\n  return svg;\n}\n","/**\n * Attachment Manager\n *\n * Handles file selection, validation, preview generation, and content part creation\n * for the composer attachment feature. Supports both images and documents.\n */\n\nimport { createElement } from \"./dom\";\nimport { renderLucideIcon } from \"./icons\";\nimport type {\n  AgentWidgetAttachmentsConfig,\n  ContentPart,\n  ImageContentPart,\n  FileContentPart\n} from \"../types\";\nimport {\n  fileToContentPart,\n  validateFile,\n  isImageFile,\n  getFileTypeName,\n  ALL_SUPPORTED_MIME_TYPES\n} from \"./content\";\n\n/**\n * Pending attachment with preview\n */\nexport interface PendingAttachment {\n  id: string;\n  file: File;\n  previewUrl: string | null; // null for non-image files\n  contentPart: ImageContentPart | FileContentPart;\n}\n\n/**\n * Attachment manager configuration\n */\nexport interface AttachmentManagerConfig {\n  allowedTypes?: string[];\n  maxFileSize?: number;\n  maxFiles?: number;\n  onFileRejected?: (file: File, reason: \"type\" | \"size\" | \"count\") => void;\n  onAttachmentsChange?: (attachments: PendingAttachment[]) => void;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULTS = {\n  allowedTypes: ALL_SUPPORTED_MIME_TYPES,\n  maxFileSize: 10 * 1024 * 1024, // 10MB\n  maxFiles: 4\n};\n\n/**\n * Generate a unique ID for attachments\n */\nfunction generateAttachmentId(): string {\n  return `attach_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Get the appropriate Lucide icon name for a file type\n */\nfunction getFileIconName(mimeType: string): string {\n  if (mimeType === 'application/pdf') return 'file-text';\n  if (mimeType.startsWith('text/')) return 'file-text';\n  if (mimeType.includes('word')) return 'file-text';\n  if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'file-spreadsheet';\n  if (mimeType === 'application/json') return 'file-code';\n  return 'file';\n}\n\n/**\n * Creates and manages attachments for the composer\n */\nexport class AttachmentManager {\n  private attachments: PendingAttachment[] = [];\n  private config: Required<\n    Pick<AttachmentManagerConfig, \"allowedTypes\" | \"maxFileSize\" | \"maxFiles\">\n  > &\n    Pick<AttachmentManagerConfig, \"onFileRejected\" | \"onAttachmentsChange\">;\n  private previewsContainer: HTMLElement | null = null;\n\n  constructor(config: AttachmentManagerConfig = {}) {\n    this.config = {\n      allowedTypes: config.allowedTypes ?? DEFAULTS.allowedTypes,\n      maxFileSize: config.maxFileSize ?? DEFAULTS.maxFileSize,\n      maxFiles: config.maxFiles ?? DEFAULTS.maxFiles,\n      onFileRejected: config.onFileRejected,\n      onAttachmentsChange: config.onAttachmentsChange\n    };\n  }\n\n  /**\n   * Set the previews container element\n   */\n  setPreviewsContainer(container: HTMLElement | null): void {\n    this.previewsContainer = container;\n  }\n\n  /**\n   * Update the configuration (e.g., when allowed types change)\n   */\n  updateConfig(config: Partial<AttachmentManagerConfig>): void {\n    if (config.allowedTypes !== undefined) {\n      this.config.allowedTypes = config.allowedTypes.length > 0 ? config.allowedTypes : DEFAULTS.allowedTypes;\n    }\n    if (config.maxFileSize !== undefined) {\n      this.config.maxFileSize = config.maxFileSize;\n    }\n    if (config.maxFiles !== undefined) {\n      this.config.maxFiles = config.maxFiles;\n    }\n    if (config.onFileRejected !== undefined) {\n      this.config.onFileRejected = config.onFileRejected;\n    }\n    if (config.onAttachmentsChange !== undefined) {\n      this.config.onAttachmentsChange = config.onAttachmentsChange;\n    }\n  }\n\n  /**\n   * Get current attachments\n   */\n  getAttachments(): PendingAttachment[] {\n    return [...this.attachments];\n  }\n\n  /**\n   * Get content parts for all attachments\n   */\n  getContentParts(): ContentPart[] {\n    return this.attachments.map((a) => a.contentPart);\n  }\n\n  /**\n   * Check if there are any attachments\n   */\n  hasAttachments(): boolean {\n    return this.attachments.length > 0;\n  }\n\n  /**\n   * Get the number of attachments\n   */\n  count(): number {\n    return this.attachments.length;\n  }\n\n  /**\n   * Handle file input change event\n   */\n  async handleFileSelect(files: FileList | null): Promise<void> {\n    if (!files || files.length === 0) return;\n    await this.handleFiles(Array.from(files));\n  }\n\n  /**\n   * Handle an array of files (e.g., clipboard image paste)\n   */\n  async handleFiles(files: readonly File[]): Promise<void> {\n    if (!files.length) return;\n\n    for (const file of files) {\n      // Check if we've hit the max files limit\n      if (this.attachments.length >= this.config.maxFiles) {\n        this.config.onFileRejected?.(file, \"count\");\n        continue;\n      }\n\n      // Validate the file\n      const validation = validateFile(\n        file,\n        this.config.allowedTypes,\n        this.config.maxFileSize\n      );\n\n      if (!validation.valid) {\n        const reason = validation.error?.includes(\"type\") ? \"type\" : \"size\";\n        this.config.onFileRejected?.(file, reason);\n        continue;\n      }\n\n      try {\n        // Convert to content part (handles both images and files)\n        const contentPart = await fileToContentPart(file);\n\n        // Create preview URL only for images\n        const previewUrl = isImageFile(file) ? URL.createObjectURL(file) : null;\n\n        const attachment: PendingAttachment = {\n          id: generateAttachmentId(),\n          file,\n          previewUrl,\n          contentPart\n        };\n\n        this.attachments.push(attachment);\n        this.renderPreview(attachment);\n      } catch (error) {\n        console.error(\"[AttachmentManager] Failed to process file:\", error);\n      }\n    }\n\n    this.updatePreviewsVisibility();\n    this.config.onAttachmentsChange?.(this.getAttachments());\n  }\n\n  /**\n   * Remove an attachment by ID\n   */\n  removeAttachment(id: string): void {\n    const index = this.attachments.findIndex((a) => a.id === id);\n    if (index === -1) return;\n\n    const attachment = this.attachments[index];\n\n    // Revoke the object URL to free memory (only for images)\n    if (attachment.previewUrl) {\n      URL.revokeObjectURL(attachment.previewUrl);\n    }\n\n    // Remove from array\n    this.attachments.splice(index, 1);\n\n    // Remove from DOM\n    const previewEl = this.previewsContainer?.querySelector(\n      `[data-attachment-id=\"${id}\"]`\n    );\n    if (previewEl) {\n      previewEl.remove();\n    }\n\n    this.updatePreviewsVisibility();\n    this.config.onAttachmentsChange?.(this.getAttachments());\n  }\n\n  /**\n   * Clear all attachments\n   */\n  clearAttachments(): void {\n    // Revoke all object URLs\n    for (const attachment of this.attachments) {\n      if (attachment.previewUrl) {\n        URL.revokeObjectURL(attachment.previewUrl);\n      }\n    }\n\n    this.attachments = [];\n\n    // Clear the previews container\n    if (this.previewsContainer) {\n      this.previewsContainer.innerHTML = \"\";\n    }\n\n    this.updatePreviewsVisibility();\n    this.config.onAttachmentsChange?.(this.getAttachments());\n  }\n\n  /**\n   * Render a preview for an attachment (image thumbnail or file icon)\n   */\n  private renderPreview(attachment: PendingAttachment): void {\n    if (!this.previewsContainer) return;\n\n    const isImage = isImageFile(attachment.file);\n\n    const previewWrapper = createElement(\n      \"div\",\n      \"persona-attachment-preview persona-relative persona-inline-block\"\n    );\n    previewWrapper.setAttribute(\"data-attachment-id\", attachment.id);\n    previewWrapper.style.width = \"48px\";\n    previewWrapper.style.height = \"48px\";\n\n    if (isImage && attachment.previewUrl) {\n      // Render image thumbnail\n      const img = createElement(\"img\") as HTMLImageElement;\n      img.src = attachment.previewUrl;\n      img.alt = attachment.file.name;\n      img.className =\n        \"persona-w-full persona-h-full persona-object-cover persona-rounded-lg persona-border persona-border-gray-200\";\n      img.style.width = \"48px\";\n      img.style.height = \"48px\";\n      img.style.objectFit = \"cover\";\n      img.style.borderRadius = \"8px\";\n      previewWrapper.appendChild(img);\n    } else {\n      // Render file icon with type label\n      const filePreview = createElement(\"div\");\n      filePreview.style.width = \"48px\";\n      filePreview.style.height = \"48px\";\n      filePreview.style.borderRadius = \"8px\";\n      filePreview.style.backgroundColor = \"var(--persona-container, #f3f4f6)\";\n      filePreview.style.border = \"1px solid var(--persona-border, #e5e7eb)\";\n      filePreview.style.display = \"flex\";\n      filePreview.style.flexDirection = \"column\";\n      filePreview.style.alignItems = \"center\";\n      filePreview.style.justifyContent = \"center\";\n      filePreview.style.gap = \"2px\";\n      filePreview.style.overflow = \"hidden\";\n\n      // File icon\n      const iconName = getFileIconName(attachment.file.type);\n      const fileIcon = renderLucideIcon(iconName, 20, \"var(--persona-muted, #6b7280)\", 1.5);\n      if (fileIcon) {\n        filePreview.appendChild(fileIcon);\n      }\n\n      // File type label\n      const typeLabel = createElement(\"span\");\n      typeLabel.textContent = getFileTypeName(attachment.file.type, attachment.file.name);\n      typeLabel.style.fontSize = \"8px\";\n      typeLabel.style.fontWeight = \"600\";\n      typeLabel.style.color = \"var(--persona-muted, #6b7280)\";\n      typeLabel.style.textTransform = \"uppercase\";\n      typeLabel.style.lineHeight = \"1\";\n      filePreview.appendChild(typeLabel);\n\n      previewWrapper.appendChild(filePreview);\n    }\n\n    // Create remove button\n    const removeBtn = createElement(\n      \"button\",\n      \"persona-attachment-remove persona-absolute persona-flex persona-items-center persona-justify-center\"\n    ) as HTMLButtonElement;\n    removeBtn.type = \"button\";\n    removeBtn.setAttribute(\"aria-label\", \"Remove attachment\");\n    removeBtn.style.position = \"absolute\";\n    removeBtn.style.top = \"-4px\";\n    removeBtn.style.right = \"-4px\";\n    removeBtn.style.width = \"18px\";\n    removeBtn.style.height = \"18px\";\n    removeBtn.style.borderRadius = \"50%\";\n    removeBtn.style.backgroundColor = \"var(--persona-palette-colors-black-alpha-60, rgba(0, 0, 0, 0.6))\";\n    removeBtn.style.border = \"none\";\n    removeBtn.style.cursor = \"pointer\";\n    removeBtn.style.display = \"flex\";\n    removeBtn.style.alignItems = \"center\";\n    removeBtn.style.justifyContent = \"center\";\n    removeBtn.style.padding = \"0\";\n\n    // Add X icon\n    const xIcon = renderLucideIcon(\"x\", 10, \"var(--persona-text-inverse, #ffffff)\", 2);\n    if (xIcon) {\n      removeBtn.appendChild(xIcon);\n    } else {\n      removeBtn.textContent = \"×\";\n      removeBtn.style.color = \"var(--persona-text-inverse, #ffffff)\";\n      removeBtn.style.fontSize = \"14px\";\n      removeBtn.style.lineHeight = \"1\";\n    }\n\n    // Remove on click\n    removeBtn.addEventListener(\"click\", (e) => {\n      e.preventDefault();\n      e.stopPropagation();\n      this.removeAttachment(attachment.id);\n    });\n\n    previewWrapper.appendChild(removeBtn);\n    this.previewsContainer.appendChild(previewWrapper);\n  }\n\n  /**\n   * Update the visibility of the previews container\n   */\n  private updatePreviewsVisibility(): void {\n    if (!this.previewsContainer) return;\n    this.previewsContainer.style.display =\n      this.attachments.length > 0 ? \"flex\" : \"none\";\n  }\n\n  /**\n   * Create an AttachmentManager from widget config\n   */\n  static fromConfig(\n    config?: AgentWidgetAttachmentsConfig,\n    onAttachmentsChange?: (attachments: PendingAttachment[]) => void\n  ): AttachmentManager {\n    return new AttachmentManager({\n      allowedTypes: config?.allowedTypes,\n      maxFileSize: config?.maxFileSize,\n      maxFiles: config?.maxFiles,\n      onFileRejected: config?.onFileRejected,\n      onAttachmentsChange\n    });\n  }\n}\n","const isObject = (value: unknown): value is Record<string, unknown> =>\n  typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/**\n * Deep-merge plain objects. Arrays and non-objects are replaced by override.\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n  base: T | undefined,\n  override: Record<string, unknown> | undefined\n): T | Record<string, unknown> | undefined {\n  if (!base) return override;\n  if (!override) return base;\n\n  const merged: Record<string, unknown> = { ...base };\n\n  for (const [key, value] of Object.entries(override)) {\n    const existing = merged[key];\n    if (isObject(existing) && isObject(value)) {\n      merged[key] = deepMerge(existing, value);\n    } else {\n      merged[key] = value;\n    }\n  }\n\n  return merged;\n}\n","import type { AgentWidgetConfig, AgentWidgetLauncherConfig } from \"./types\";\nimport type { DeepPartial, PersonaTheme } from \"./types/theme\";\nimport { deepMerge } from \"./utils/deep-merge\";\n\n/**\n * Default width for the floating launcher panel (when not overridden).\n * Benchmarks: many chat products use ~300–400px; 400px is a frequent “standard” default.\n * We use 440px to better fit code/JSON and structured replies while staying responsive via `min(..., 100vw)`.\n */\nexport const DEFAULT_FLOATING_LAUNCHER_WIDTH = \"min(440px, calc(100vw - 24px))\";\n\n/** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */\nexport const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = \"440px\";\n\nexport const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {\n  enabled: true,\n  mountMode: \"floating\",\n  dock: {\n    side: \"right\",\n    width: \"420px\",\n  },\n  title: \"Chat Assistant\",\n  subtitle: \"Here to help you get answers fast\",\n  agentIconText: \"💬\",\n  agentIconName: \"bot\",\n  headerIconName: \"bot\",\n  position: \"bottom-right\",\n  width: DEFAULT_FLOATING_LAUNCHER_WIDTH,\n  heightOffset: 0,\n  autoExpand: false,\n  callToActionIconHidden: false,\n  agentIconSize: \"40px\",\n  headerIconSize: \"40px\",\n  closeButtonSize: \"32px\",\n  // Zero out browser-default <button> padding so the icon gets the full\n  // 32x32 content box, matching clearChat.paddingX/Y below. Without this,\n  // UA stylesheets add ~1-2px vertical and ~6px horizontal padding that\n  // eats into the border-box width and shrinks the rendered icon.\n  closeButtonPaddingX: \"0px\",\n  closeButtonPaddingY: \"0px\",\n  callToActionIconName: \"arrow-up-right\",\n  callToActionIconText: \"\",\n  callToActionIconSize: \"32px\",\n  callToActionIconPadding: \"5px\",\n  callToActionIconColor: undefined,\n  callToActionIconBackgroundColor: undefined,\n  // closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.\n  closeButtonBackgroundColor: \"transparent\",\n  clearChat: {\n    backgroundColor: \"transparent\",\n    borderColor: \"transparent\",\n    enabled: true,\n    placement: \"inline\",\n    iconName: \"refresh-cw\",\n    size: \"32px\",\n    showTooltip: true,\n    tooltipText: \"Clear chat\",\n    paddingX: \"0px\",\n    paddingY: \"0px\",\n  },\n  headerIconHidden: false,\n  border: undefined,\n  shadow:\n    \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)\",\n};\n\n/**\n * Default widget configuration\n * Single source of truth for all default values\n */\nexport const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {\n  apiUrl: \"https://api.runtype.com/api/chat/dispatch\",\n  // Client token mode defaults (optional, only used when clientToken is set)\n  clientToken: undefined,\n  agentId: undefined,\n  target: undefined,\n  theme: undefined,\n  darkTheme: undefined,\n  colorScheme: \"light\",\n  launcher: DEFAULT_LAUNCHER_CONFIG,\n  copy: {\n    welcomeTitle: \"Hello 👋\",\n    welcomeSubtitle: \"Ask anything about your account or products.\",\n    inputPlaceholder: \"How can I help...\",\n    sendButtonLabel: \"Send\",\n  },\n  sendButton: {\n    borderWidth: \"0px\",\n    paddingX: \"12px\",\n    paddingY: \"10px\",\n    borderColor: undefined,\n    useIcon: true,\n    iconText: \"↑\",\n    size: \"40px\",\n    showTooltip: true,\n    tooltipText: \"Send message\",\n    iconName: \"send\",\n  },\n  statusIndicator: {\n    visible: true,\n    idleText: \"Online\",\n    connectingText: \"Connecting…\",\n    connectedText: \"Streaming…\",\n    errorText: \"Offline\",\n  },\n  voiceRecognition: {\n    enabled: true,\n    pauseDuration: 2000,\n    iconName: \"mic\",\n    iconSize: \"39px\",\n    borderWidth: \"0px\",\n    paddingX: \"9px\",\n    paddingY: \"14px\",\n    iconColor: undefined,\n    backgroundColor: \"transparent\",\n    borderColor: \"transparent\",\n    recordingIconColor: undefined,\n    recordingBackgroundColor: undefined,\n    recordingBorderColor: \"transparent\",\n    showTooltip: true,\n    tooltipText: \"Start voice recognition\",\n  },\n  features: {\n    showReasoning: true,\n    showToolCalls: true,\n    scrollToBottom: {\n      enabled: true,\n      iconName: \"arrow-down\",\n      label: \"\",\n    },\n    scrollBehavior: {\n      // ChatGPT-style default: pin the just-sent message near the top and let\n      // the reply stream into the space below. Restore the old \"stick to the\n      // bottom\" behavior with `mode: \"follow\"`.\n      mode: \"anchor-top\",\n      anchorTopOffset: 16,\n      // Surface the unread count + \"streaming below\" hint while pinned, so the\n      // reader still sees activity arriving off-screen under the pinned turn.\n      // (Suppressed by default historically; opted on alongside the anchor-top\n      // default so the default UX keeps the affordance.)\n      showActivityWhilePinned: true,\n    },\n    toolCallDisplay: {\n      collapsedMode: \"tool-call\",\n      activePreview: false,\n      grouped: false,\n      groupedMode: \"stack\",\n      previewMaxLines: 3,\n      expandable: true,\n      loadingAnimation: \"none\",\n    },\n    reasoningDisplay: {\n      activePreview: false,\n      previewMaxLines: 3,\n      expandable: true,\n      loadingAnimation: \"none\",\n    },\n    streamAnimation: {\n      type: \"none\",\n      placeholder: \"none\",\n      speed: 120,\n      duration: 1800,\n    },\n    askUserQuestion: {\n      enabled: true,\n      slideInMs: 180,\n      freeTextLabel: \"Other…\",\n      freeTextPlaceholder: \"Type your answer…\",\n      submitLabel: \"Send\",\n    },\n  },\n  suggestionChips: [\n    \"What can you help me with?\",\n    \"Tell me about your features\",\n    \"How does this work?\",\n  ],\n  suggestionChipsConfig: {\n    fontFamily: \"sans-serif\",\n    fontWeight: \"500\",\n    paddingX: \"12px\",\n    paddingY: \"6px\",\n  },\n  layout: {\n    header: {\n      layout: \"default\",\n      showIcon: true,\n      showTitle: true,\n      showSubtitle: true,\n      showCloseButton: true,\n      showClearChat: true,\n    },\n    messages: {\n      layout: \"bubble\",\n      avatar: {\n        show: false,\n        position: \"left\",\n      },\n      timestamp: {\n        show: false,\n        position: \"below\",\n      },\n      groupConsecutive: false,\n    },\n    slots: {},\n  },\n  markdown: {\n    options: {\n      gfm: true,\n      breaks: true,\n    },\n    disableDefaultStyles: false,\n  },\n  messageActions: {\n    enabled: true,\n    showCopy: true,\n    showUpvote: false, // Requires backend - disabled by default\n    showDownvote: false, // Requires backend - disabled by default\n    visibility: \"hover\",\n    align: \"right\",\n    layout: \"pill-inside\",\n  },\n  debug: false,\n};\n\nfunction mergeThemePartials(\n  base: DeepPartial<PersonaTheme> | undefined,\n  override: DeepPartial<PersonaTheme> | undefined\n): DeepPartial<PersonaTheme> | undefined {\n  if (!base && !override) return undefined;\n  if (!base) return override;\n  if (!override) return base;\n  return deepMerge(\n    base as Record<string, unknown>,\n    override as Record<string, unknown>\n  ) as DeepPartial<PersonaTheme>;\n}\n\n/**\n * Helper to deep merge user config with defaults\n * This ensures all default values are present while allowing selective overrides\n */\nexport function mergeWithDefaults(\n  config?: Partial<AgentWidgetConfig>\n): Partial<AgentWidgetConfig> {\n  if (!config) return DEFAULT_WIDGET_CONFIG;\n\n  return {\n    ...DEFAULT_WIDGET_CONFIG,\n    ...config,\n    theme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.theme, config.theme),\n    darkTheme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.darkTheme, config.darkTheme),\n    launcher: {\n      ...DEFAULT_WIDGET_CONFIG.launcher,\n      ...config.launcher,\n      dock: {\n        ...DEFAULT_WIDGET_CONFIG.launcher?.dock,\n        ...config.launcher?.dock,\n      },\n      clearChat: {\n        ...DEFAULT_WIDGET_CONFIG.launcher?.clearChat,\n        ...config.launcher?.clearChat,\n      },\n    },\n    copy: {\n      ...DEFAULT_WIDGET_CONFIG.copy,\n      ...config.copy,\n    },\n    sendButton: {\n      ...DEFAULT_WIDGET_CONFIG.sendButton,\n      ...config.sendButton,\n    },\n    statusIndicator: {\n      ...DEFAULT_WIDGET_CONFIG.statusIndicator,\n      ...config.statusIndicator,\n    },\n    voiceRecognition: {\n      ...DEFAULT_WIDGET_CONFIG.voiceRecognition,\n      ...config.voiceRecognition,\n    },\n    features: (() => {\n      const da = DEFAULT_WIDGET_CONFIG.features?.artifacts;\n      const ca = config.features?.artifacts;\n      const dsb = DEFAULT_WIDGET_CONFIG.features?.scrollToBottom;\n      const csb = config.features?.scrollToBottom;\n      const dsc = DEFAULT_WIDGET_CONFIG.features?.scrollBehavior;\n      const csc = config.features?.scrollBehavior;\n      const dsa = DEFAULT_WIDGET_CONFIG.features?.streamAnimation;\n      const csa = config.features?.streamAnimation;\n      const dau = DEFAULT_WIDGET_CONFIG.features?.askUserQuestion;\n      const cau = config.features?.askUserQuestion;\n      const mergedArtifacts =\n        da === undefined && ca === undefined\n          ? undefined\n          : {\n              ...da,\n              ...ca,\n              layout: {\n                ...da?.layout,\n                ...ca?.layout,\n              },\n            };\n      const mergedScrollToBottom =\n        dsb === undefined && csb === undefined\n          ? undefined\n          : {\n              ...dsb,\n              ...csb,\n            };\n      const mergedScrollBehavior =\n        dsc === undefined && csc === undefined\n          ? undefined\n          : {\n              ...dsc,\n              ...csc,\n            };\n      const mergedStreamAnimation =\n        dsa === undefined && csa === undefined\n          ? undefined\n          : {\n              ...dsa,\n              ...csa,\n            };\n      const mergedAskUserQuestion =\n        dau === undefined && cau === undefined\n          ? undefined\n          : {\n              ...dau,\n              ...cau,\n              styles: {\n                ...dau?.styles,\n                ...cau?.styles,\n              },\n            };\n      return {\n        ...DEFAULT_WIDGET_CONFIG.features,\n        ...config.features,\n        ...(mergedScrollToBottom !== undefined ? { scrollToBottom: mergedScrollToBottom } : {}),\n        ...(mergedScrollBehavior !== undefined ? { scrollBehavior: mergedScrollBehavior } : {}),\n        ...(mergedArtifacts !== undefined ? { artifacts: mergedArtifacts } : {}),\n        ...(mergedStreamAnimation !== undefined ? { streamAnimation: mergedStreamAnimation } : {}),\n        ...(mergedAskUserQuestion !== undefined ? { askUserQuestion: mergedAskUserQuestion } : {}),\n      };\n    })(),\n    suggestionChips: config.suggestionChips ?? DEFAULT_WIDGET_CONFIG.suggestionChips,\n    suggestionChipsConfig: {\n      ...DEFAULT_WIDGET_CONFIG.suggestionChipsConfig,\n      ...config.suggestionChipsConfig,\n    },\n    layout: {\n      ...DEFAULT_WIDGET_CONFIG.layout,\n      ...config.layout,\n      header: {\n        ...DEFAULT_WIDGET_CONFIG.layout?.header,\n        ...config.layout?.header,\n      },\n      messages: {\n        ...DEFAULT_WIDGET_CONFIG.layout?.messages,\n        ...config.layout?.messages,\n        avatar: {\n          ...DEFAULT_WIDGET_CONFIG.layout?.messages?.avatar,\n          ...config.layout?.messages?.avatar,\n        },\n        timestamp: {\n          ...DEFAULT_WIDGET_CONFIG.layout?.messages?.timestamp,\n          ...config.layout?.messages?.timestamp,\n        },\n      },\n      slots: {\n        ...DEFAULT_WIDGET_CONFIG.layout?.slots,\n        ...config.layout?.slots,\n      },\n    },\n    markdown: {\n      ...DEFAULT_WIDGET_CONFIG.markdown,\n      ...config.markdown,\n      options: {\n        ...DEFAULT_WIDGET_CONFIG.markdown?.options,\n        ...config.markdown?.options,\n      },\n    },\n    messageActions: {\n      ...DEFAULT_WIDGET_CONFIG.messageActions,\n      ...config.messageActions,\n    },\n  };\n}\n","import type {\n  DeepPartial,\n  PersonaTheme,\n  ResolvedToken,\n  ThemeValidationResult,\n  ThemeValidationError,\n  CreateThemeOptions,\n  ComponentTokens,\n  SemanticTokens,\n} from '../types/theme';\nimport {\n  DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,\n  DEFAULT_FLOATING_LAUNCHER_WIDTH,\n} from '../defaults';\n\n// Detached/docked panel defaults, shared by the panel token defaults, the alias\n// fallbacks below, host-layout, the theme editor, and the artifact gate.\nexport const DEFAULT_PANEL_INSET = '16px';\nexport const DEFAULT_PANEL_CANVAS_BACKGROUND = 'transparent';\n\nexport const DEFAULT_PALETTE = {\n  colors: {\n    primary: {\n      50: '#ffffff',\n      100: '#f5f5f5',\n      200: '#d4d4d4',\n      300: '#a3a3a3',\n      400: '#737373',\n      500: '#171717',\n      600: '#0f0f0f',\n      700: '#0a0a0a',\n      800: '#050505',\n      900: '#030303',\n      950: '#000000',\n    },\n    secondary: {\n      50: '#f5f3ff',\n      100: '#ede9fe',\n      200: '#ddd6fe',\n      300: '#c4b5fd',\n      400: '#a78bfa',\n      500: '#8b5cf6',\n      600: '#7c3aed',\n      700: '#6d28d9',\n      800: '#5b21b6',\n      900: '#4c1d95',\n      950: '#2e1065',\n    },\n    accent: {\n      50: '#ecfeff',\n      100: '#cffafe',\n      200: '#a5f3fc',\n      300: '#67e8f9',\n      400: '#22d3ee',\n      500: '#06b6d4',\n      600: '#0891b2',\n      700: '#0e7490',\n      800: '#155e75',\n      900: '#164e63',\n      950: '#083344',\n    },\n    gray: {\n      50: '#f9fafb',\n      100: '#f3f4f6',\n      200: '#e5e7eb',\n      300: '#d1d5db',\n      400: '#9ca3af',\n      500: '#6b7280',\n      600: '#4b5563',\n      700: '#374151',\n      800: '#1f2937',\n      900: '#111827',\n      950: '#030712',\n    },\n    success: {\n      50: '#f0fdf4',\n      100: '#dcfce7',\n      200: '#bbf7d0',\n      300: '#86efac',\n      400: '#4ade80',\n      500: '#22c55e',\n      600: '#16a34a',\n      700: '#15803d',\n      800: '#166534',\n      900: '#14532d',\n    },\n    warning: {\n      50: '#fefce8',\n      100: '#fef9c3',\n      200: '#fef08a',\n      300: '#fde047',\n      400: '#facc15',\n      500: '#eab308',\n      600: '#ca8a04',\n      700: '#a16207',\n      800: '#854d0e',\n      900: '#713f12',\n    },\n    error: {\n      50: '#fef2f2',\n      100: '#fee2e2',\n      200: '#fecaca',\n      300: '#fca5a5',\n      400: '#f87171',\n      500: '#ef4444',\n      600: '#dc2626',\n      700: '#b91c1c',\n      800: '#991b1b',\n      900: '#7f1d1d',\n    },\n    info: {\n      50: '#eff6ff',\n      100: '#dbeafe',\n      200: '#bfdbfe',\n      300: '#93c5fd',\n      400: '#60a5fa',\n      500: '#3b82f6',\n      600: '#2563eb',\n      700: '#1d4ed8',\n      800: '#1e40af',\n      900: '#1e3a8a',\n      950: '#172554',\n    },\n  },\n  spacing: {\n    0: '0px',\n    1: '0.25rem',\n    2: '0.5rem',\n    3: '0.75rem',\n    4: '1rem',\n    5: '1.25rem',\n    6: '1.5rem',\n    8: '2rem',\n    10: '2.5rem',\n    12: '3rem',\n    16: '4rem',\n    20: '5rem',\n    24: '6rem',\n    32: '8rem',\n    40: '10rem',\n    48: '12rem',\n    56: '14rem',\n    64: '16rem',\n  },\n  typography: {\n    fontFamily: {\n      sans: 'system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n      serif: 'Georgia, Cambria, \"Times New Roman\", Times, serif',\n      mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',\n    },\n    fontSize: {\n      xs: '0.75rem',\n      sm: '0.875rem',\n      base: '1rem',\n      lg: '1.125rem',\n      xl: '1.25rem',\n      '2xl': '1.5rem',\n      '3xl': '1.875rem',\n      '4xl': '2.25rem',\n    },\n    fontWeight: {\n      normal: '400',\n      medium: '500',\n      semibold: '600',\n      bold: '700',\n    },\n    lineHeight: {\n      tight: '1.25',\n      normal: '1.5',\n      relaxed: '1.625',\n    },\n  },\n  shadows: {\n    none: 'none',\n    sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',\n    md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',\n    lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',\n    xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',\n    '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',\n  },\n  borders: {\n    none: 'none',\n    sm: '1px solid',\n    md: '2px solid',\n    lg: '4px solid',\n  },\n  radius: {\n    none: '0px',\n    sm: '0.125rem',\n    md: '0.375rem',\n    lg: '0.5rem',\n    xl: '0.75rem',\n    '2xl': '1rem',\n    full: '9999px',\n  },\n};\n\nexport const DEFAULT_SEMANTIC: SemanticTokens = {\n  colors: {\n    primary: 'palette.colors.primary.500',\n    secondary: 'palette.colors.secondary.500',\n    // Links/Focus role: solid primary\n    accent: 'palette.colors.primary.600',\n    // Surfaces role: soft gray\n    surface: 'palette.colors.gray.50',\n    background: 'palette.colors.gray.50',\n    container: 'palette.colors.gray.50',\n    text: 'palette.colors.gray.900',\n    textMuted: 'palette.colors.gray.500',\n    textInverse: 'palette.colors.gray.50',\n    // Borders role: soft gray\n    border: 'palette.colors.gray.200',\n    divider: 'palette.colors.gray.200',\n    interactive: {\n      // Primary Actions role: solid primary\n      default: 'palette.colors.primary.600',\n      hover: 'palette.colors.primary.700',\n      // Links/Focus role: solid primary\n      focus: 'palette.colors.primary.600',\n      active: 'palette.colors.primary.600',\n      disabled: 'palette.colors.gray.300',\n    },\n    feedback: {\n      success: 'palette.colors.success.500',\n      warning: 'palette.colors.warning.500',\n      error: 'palette.colors.error.500',\n      info: 'palette.colors.info.500',\n    },\n  },\n  spacing: {\n    xs: 'palette.spacing.1',\n    sm: 'palette.spacing.2',\n    md: 'palette.spacing.4',\n    lg: 'palette.spacing.6',\n    xl: 'palette.spacing.8',\n    '2xl': 'palette.spacing.10',\n  },\n  typography: {\n    fontFamily: 'palette.typography.fontFamily.sans',\n    fontSize: 'palette.typography.fontSize.base',\n    fontWeight: 'palette.typography.fontWeight.normal',\n    lineHeight: 'palette.typography.lineHeight.normal',\n  },\n};\n\nexport const DEFAULT_COMPONENTS: ComponentTokens = {\n  button: {\n    primary: {\n      // Primary Actions role: solid primary\n      background: 'palette.colors.primary.500',\n      foreground: 'palette.colors.primary.50',\n      borderRadius: 'palette.radius.lg',\n      padding: 'semantic.spacing.md',\n    },\n    secondary: {\n      background: 'semantic.colors.surface',\n      foreground: 'semantic.colors.secondary',\n      borderRadius: 'palette.radius.lg',\n      padding: 'semantic.spacing.md',\n    },\n    ghost: {\n      background: 'transparent',\n      foreground: 'semantic.colors.text',\n      borderRadius: 'palette.radius.md',\n      padding: 'semantic.spacing.sm',\n    },\n  },\n  input: {\n    // Input role: soft gray\n    background: 'palette.colors.gray.50',\n    placeholder: 'palette.colors.gray.400',\n    borderRadius: 'palette.radius.lg',\n    padding: 'semantic.spacing.md',\n    focus: {\n      border: 'palette.colors.gray.400',\n      ring: 'palette.colors.gray.400',\n    },\n  },\n  launcher: {\n    background: 'palette.colors.primary.500',\n    foreground: 'palette.colors.primary.50',\n    border: 'palette.colors.gray.200',\n    size: '60px',\n    iconSize: '28px',\n    borderRadius: 'palette.radius.full',\n    shadow: 'palette.shadows.lg',\n  },\n  panel: {\n    width: DEFAULT_FLOATING_LAUNCHER_WIDTH,\n    maxWidth: DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,\n    height: '600px',\n    maxHeight: 'calc(100vh - 80px)',\n    borderRadius: 'palette.radius.xl',\n    shadow: 'palette.shadows.xl',\n    inset: DEFAULT_PANEL_INSET,\n    canvasBackground: DEFAULT_PANEL_CANVAS_BACKGROUND,\n  },\n  header: {\n    // Header role: solid primary\n    background: 'palette.colors.primary.500',\n    border: 'palette.colors.primary.600',\n    borderRadius: 'palette.radius.xl palette.radius.xl 0 0',\n    padding: 'semantic.spacing.md',\n    iconBackground: 'palette.colors.primary.600',\n    iconForeground: 'palette.colors.primary.50',\n    titleForeground: 'palette.colors.primary.50',\n    subtitleForeground: 'palette.colors.primary.200',\n    actionIconForeground: 'palette.colors.primary.200',\n  },\n  message: {\n    user: {\n      // User Messages role: solid primary\n      background: 'palette.colors.primary.500',\n      text: 'palette.colors.primary.50',\n      borderRadius: 'palette.radius.lg',\n      shadow: 'palette.shadows.sm',\n    },\n    assistant: {\n      // Assistant Messages role: soft gray\n      background: 'palette.colors.gray.50',\n      text: 'palette.colors.gray.900',\n      borderRadius: 'palette.radius.lg',\n      border: 'palette.colors.gray.200',\n      shadow: 'palette.shadows.sm',\n    },\n    border: 'semantic.colors.border',\n  },\n  introCard: {\n    // Defaults preserve the legacy `persona-shadow-sm` look exactly so existing\n    // pages render unchanged when no token is set.\n    background: 'semantic.colors.surface',\n    borderRadius: 'palette.radius.2xl',\n    padding: 'semantic.spacing.lg',\n    shadow: '0 5px 15px rgba(15, 23, 42, 0.08)',\n  },\n  toolBubble: {\n    shadow: 'palette.shadows.sm',\n  },\n  reasoningBubble: {\n    shadow: 'palette.shadows.sm',\n  },\n  composer: {\n    shadow: 'palette.shadows.none',\n  },\n  markdown: {\n    inlineCode: {\n      background: 'palette.colors.gray.50',\n      foreground: 'palette.colors.gray.900',\n    },\n    link: {\n      // Links/Focus role: solid primary\n      foreground: 'palette.colors.primary.600',\n    },\n    prose: {\n      fontFamily: 'inherit',\n    },\n    codeBlock: {\n      background: 'semantic.colors.container',\n      borderColor: 'semantic.colors.border',\n      textColor: 'inherit',\n    },\n    table: {\n      headerBackground: 'semantic.colors.container',\n      borderColor: 'semantic.colors.border',\n    },\n    hr: {\n      color: 'semantic.colors.divider',\n    },\n    blockquote: {\n      borderColor: 'palette.colors.gray.900',\n      background: 'transparent',\n      textColor: 'palette.colors.gray.500',\n    },\n  },\n  collapsibleWidget: {\n    container: 'palette.colors.gray.50',\n    surface: 'semantic.colors.surface',\n    border: 'semantic.colors.border',\n  },\n  voice: {\n    recording: {\n      indicator: 'palette.colors.error.500',\n      background: 'palette.colors.error.50',\n      border: 'palette.colors.error.200',\n    },\n    processing: {\n      icon: 'palette.colors.primary.500',\n      background: 'palette.colors.primary.50',\n    },\n    speaking: {\n      icon: 'palette.colors.success.500',\n    },\n  },\n  // Neutral surface card (components/approval-actions.ts). The primary action\n  // anchors to the brand primary; deny is a neutral tinted button. Consumers who\n  // themed these (or set config.approval.* color overrides) still win.\n  approval: {\n    requested: {\n      background: 'semantic.colors.surface',\n      border: 'semantic.colors.border',\n      text: 'palette.colors.gray.900',\n      shadow: '0 1px 2px 0 rgba(11, 11, 11, 0.06), 0 2px 8px 0 rgba(11, 11, 11, 0.04)',\n    },\n    approve: {\n      background: 'semantic.colors.primary',\n      foreground: 'semantic.colors.textInverse',\n      borderRadius: 'palette.radius.md',\n      padding: 'semantic.spacing.sm',\n    },\n    deny: {\n      background: 'semantic.colors.container',\n      foreground: 'semantic.colors.text',\n      borderRadius: 'palette.radius.md',\n      padding: 'semantic.spacing.sm',\n    },\n  },\n  attachment: {\n    image: {\n      background: 'palette.colors.gray.100',\n      border: 'palette.colors.gray.200',\n    },\n  },\n  scrollToBottom: {\n    background: 'components.button.primary.background',\n    foreground: 'components.button.primary.foreground',\n    border: 'semantic.colors.primary',\n    size: '40px',\n    borderRadius: 'palette.radius.full',\n    shadow: 'palette.shadows.sm',\n    padding: '0.5rem 0.875rem',\n    gap: '0.5rem',\n    fontSize: '0.875rem',\n    iconSize: '14px',\n  },\n  artifact: {\n    pane: {\n      background: 'semantic.colors.container',\n      toolbarBackground: 'semantic.colors.container',\n    },\n  },\n};\n\nexport function resolveTokenValue(theme: PersonaTheme, path: string): string | undefined {\n  if (\n    !path.startsWith('palette.') &&\n    !path.startsWith('semantic.') &&\n    !path.startsWith('components.')\n  ) {\n    return path;\n  }\n\n  const parts = path.split('.');\n  let current: any = theme;\n\n  for (const part of parts) {\n    if (current === undefined || current === null) {\n      return undefined;\n    }\n    current = current[part];\n  }\n\n  if (\n    typeof current === 'string' &&\n    (current.startsWith('palette.') ||\n      current.startsWith('semantic.') ||\n      current.startsWith('components.'))\n  ) {\n    return resolveTokenValue(theme, current);\n  }\n\n  return current;\n}\n\nexport function resolveTokens(theme: PersonaTheme): Record<string, ResolvedToken> {\n  const resolved: Record<string, ResolvedToken> = {};\n\n  function resolveObject(obj: any, prefix: string) {\n    for (const [key, value] of Object.entries(obj)) {\n      const path = `${prefix}.${key}`;\n\n      if (typeof value === 'string') {\n        const resolvedValue = resolveTokenValue(theme, value);\n        if (resolvedValue !== undefined) {\n          resolved[path] = {\n            path,\n            value: resolvedValue,\n            type:\n              prefix.includes('color')\n                ? 'color'\n                : prefix.includes('spacing')\n                  ? 'spacing'\n                  : prefix.includes('typography')\n                    ? 'typography'\n                    : prefix.includes('shadow')\n                      ? 'shadow'\n                      : prefix.includes('border')\n                        ? 'border'\n                        : 'color',\n          };\n        }\n      } else if (typeof value === 'object' && value !== null) {\n        resolveObject(value, path);\n      }\n    }\n  }\n\n  resolveObject(theme.palette, 'palette');\n  resolveObject(theme.semantic, 'semantic');\n  resolveObject(theme.components, 'components');\n\n  return resolved;\n}\n\nexport function validateTheme(theme: Partial<PersonaTheme>): ThemeValidationResult {\n  const errors: ThemeValidationError[] = [];\n  const warnings: ThemeValidationError[] = [];\n\n  if (!theme.palette) {\n    errors.push({\n      path: 'palette',\n      message: 'Theme must include a palette',\n      severity: 'error',\n    });\n  }\n\n  if (!theme.semantic) {\n    warnings.push({\n      path: 'semantic',\n      message: 'No semantic tokens defined - defaults will be used',\n      severity: 'warning',\n    });\n  }\n\n  if (!theme.components) {\n    warnings.push({\n      path: 'components',\n      message: 'No component tokens defined - defaults will be used',\n      severity: 'warning',\n    });\n  }\n\n  return {\n    valid: errors.length === 0,\n    errors,\n    warnings,\n  };\n}\n\nfunction mergeRecords(\n  base: Record<string, unknown>,\n  override: Record<string, unknown>\n): Record<string, unknown> {\n  const result = { ...base };\n  for (const [key, value] of Object.entries(override)) {\n    const existing = result[key];\n    if (existing && typeof existing === 'object' && !Array.isArray(existing) &&\n        value && typeof value === 'object' && !Array.isArray(value)) {\n      result[key] = mergeRecords(\n        existing as Record<string, unknown>,\n        value as Record<string, unknown>\n      );\n    } else {\n      result[key] = value;\n    }\n  }\n  return result;\n}\n\nfunction deepMergeComponents(\n  base: ComponentTokens,\n  override?: Partial<ComponentTokens>\n): ComponentTokens {\n  if (!override) return base;\n  return mergeRecords(\n    base as unknown as Record<string, unknown>,\n    override as unknown as Record<string, unknown>\n  ) as unknown as ComponentTokens;\n}\n\nexport function createTheme(\n  userConfig?: DeepPartial<PersonaTheme>,\n  options: CreateThemeOptions = {}\n): PersonaTheme {\n  const baseTheme: PersonaTheme = {\n    palette: DEFAULT_PALETTE as PersonaTheme['palette'],\n    semantic: DEFAULT_SEMANTIC as PersonaTheme['semantic'],\n    components: DEFAULT_COMPONENTS as PersonaTheme['components'],\n  };\n\n  let theme: PersonaTheme = {\n    palette: {\n      ...baseTheme.palette,\n      ...userConfig?.palette,\n      colors: {\n        ...baseTheme.palette.colors,\n        ...userConfig?.palette?.colors,\n      },\n      spacing: {\n        ...baseTheme.palette.spacing,\n        ...userConfig?.palette?.spacing,\n      },\n      typography: {\n        ...baseTheme.palette.typography,\n        ...userConfig?.palette?.typography,\n      },\n      shadows: {\n        ...baseTheme.palette.shadows,\n        ...userConfig?.palette?.shadows,\n      },\n      borders: {\n        ...baseTheme.palette.borders,\n        ...userConfig?.palette?.borders,\n      },\n      radius: {\n        ...baseTheme.palette.radius,\n        ...userConfig?.palette?.radius,\n      },\n    },\n    semantic: {\n      ...baseTheme.semantic,\n      ...userConfig?.semantic,\n      colors: {\n        ...baseTheme.semantic.colors,\n        ...userConfig?.semantic?.colors,\n        interactive: {\n          ...baseTheme.semantic.colors.interactive,\n          ...userConfig?.semantic?.colors?.interactive,\n        },\n        feedback: {\n          ...baseTheme.semantic.colors.feedback,\n          ...userConfig?.semantic?.colors?.feedback,\n        },\n      },\n      spacing: {\n        ...baseTheme.semantic.spacing,\n        ...userConfig?.semantic?.spacing,\n      },\n      typography: {\n        ...baseTheme.semantic.typography,\n        ...userConfig?.semantic?.typography,\n      },\n    },\n    components: deepMergeComponents(\n      baseTheme.components,\n      userConfig?.components as Partial<ComponentTokens> | undefined\n    ),\n  } as PersonaTheme;\n\n  if (options.validate !== false) {\n    const validation = validateTheme(theme);\n    if (!validation.valid) {\n      throw new Error(\n        `Theme validation failed: ${validation.errors.map((e) => e.message).join(', ')}`\n      );\n    }\n  }\n\n  if (options.plugins) {\n    for (const plugin of options.plugins) {\n      theme = plugin.transform(theme);\n    }\n  }\n\n  return theme;\n}\n\nexport function themeToCssVariables(theme: PersonaTheme): Record<string, string> {\n  const resolved = resolveTokens(theme);\n  const cssVars: Record<string, string> = {};\n\n  for (const [path, token] of Object.entries(resolved)) {\n    const varName = path.replace(/\\./g, '-');\n    cssVars[`--persona-${varName}`] = token.value;\n  }\n\n  cssVars['--persona-primary'] = cssVars['--persona-semantic-colors-primary'] ?? cssVars['--persona-palette-colors-primary-500'];\n  cssVars['--persona-secondary'] = cssVars['--persona-semantic-colors-secondary'] ?? cssVars['--persona-palette-colors-secondary-500'];\n  cssVars['--persona-accent'] = cssVars['--persona-semantic-colors-accent'] ?? cssVars['--persona-palette-colors-accent-500'];\n  cssVars['--persona-surface'] = cssVars['--persona-semantic-colors-surface'] ?? cssVars['--persona-palette-colors-gray-50'];\n  cssVars['--persona-background'] = cssVars['--persona-semantic-colors-background'] ?? cssVars['--persona-palette-colors-gray-50'];\n  cssVars['--persona-container'] = cssVars['--persona-semantic-colors-container'] ?? cssVars['--persona-palette-colors-gray-100'];\n  cssVars['--persona-text'] = cssVars['--persona-semantic-colors-text'] ?? cssVars['--persona-palette-colors-gray-900'];\n  cssVars['--persona-text-muted'] = cssVars['--persona-semantic-colors-text-muted'] ?? cssVars['--persona-palette-colors-gray-500'];\n  cssVars['--persona-text-inverse'] = cssVars['--persona-semantic-colors-text-inverse'] ?? cssVars['--persona-palette-colors-gray-50'];\n  cssVars['--persona-border'] = cssVars['--persona-semantic-colors-border'] ?? cssVars['--persona-palette-colors-gray-200'];\n  cssVars['--persona-divider'] = cssVars['--persona-semantic-colors-divider'] ?? cssVars['--persona-palette-colors-gray-200'];\n  cssVars['--persona-muted'] = cssVars['--persona-text-muted'];\n\n  cssVars['--persona-voice-recording-indicator'] = cssVars['--persona-components-voice-recording-indicator'] ?? cssVars['--persona-palette-colors-error-500'];\n  cssVars['--persona-voice-recording-bg'] = cssVars['--persona-components-voice-recording-background'] ?? cssVars['--persona-palette-colors-error-50'];\n  cssVars['--persona-voice-processing-icon'] = cssVars['--persona-components-voice-processing-icon'] ?? cssVars['--persona-palette-colors-primary-500'];\n  cssVars['--persona-voice-speaking-icon'] = cssVars['--persona-components-voice-speaking-icon'] ?? cssVars['--persona-palette-colors-success-500'];\n\n  cssVars['--persona-approval-bg'] = cssVars['--persona-components-approval-requested-background'] ?? cssVars['--persona-surface'];\n  cssVars['--persona-approval-border'] = cssVars['--persona-components-approval-requested-border'] ?? cssVars['--persona-border'];\n  cssVars['--persona-approval-text'] = cssVars['--persona-components-approval-requested-text'] ?? cssVars['--persona-palette-colors-gray-900'];\n  cssVars['--persona-approval-shadow'] = cssVars['--persona-components-approval-requested-shadow'] ?? '0 1px 2px 0 rgba(11, 11, 11, 0.06), 0 2px 8px 0 rgba(11, 11, 11, 0.04)';\n  cssVars['--persona-approval-approve-bg'] = cssVars['--persona-components-approval-approve-background'] ?? cssVars['--persona-button-primary-bg'];\n  cssVars['--persona-approval-deny-bg'] = cssVars['--persona-components-approval-deny-background'] ?? cssVars['--persona-container'];\n\n  cssVars['--persona-attachment-image-bg'] = cssVars['--persona-components-attachment-image-background'] ?? cssVars['--persona-palette-colors-gray-100'];\n  cssVars['--persona-attachment-image-border'] = cssVars['--persona-components-attachment-image-border'] ?? cssVars['--persona-palette-colors-gray-200'];\n\n  // Typography shorthand aliases\n  cssVars['--persona-font-family'] = cssVars['--persona-semantic-typography-fontFamily'] ?? cssVars['--persona-palette-typography-fontFamily-sans'];\n  cssVars['--persona-font-size'] = cssVars['--persona-semantic-typography-fontSize'] ?? cssVars['--persona-palette-typography-fontSize-base'];\n  cssVars['--persona-font-weight'] = cssVars['--persona-semantic-typography-fontWeight'] ?? cssVars['--persona-palette-typography-fontWeight-normal'];\n  cssVars['--persona-line-height'] = cssVars['--persona-semantic-typography-lineHeight'] ?? cssVars['--persona-palette-typography-lineHeight-normal'];\n\n  cssVars['--persona-input-font-family'] = cssVars['--persona-font-family'];\n  cssVars['--persona-input-font-weight'] = cssVars['--persona-font-weight'];\n\n  // Radius aliases used throughout the existing widget CSS.\n  cssVars['--persona-radius-sm'] = cssVars['--persona-palette-radius-sm'] ?? '0.125rem';\n  cssVars['--persona-radius-md'] = cssVars['--persona-palette-radius-md'] ?? '0.375rem';\n  cssVars['--persona-radius-lg'] = cssVars['--persona-palette-radius-lg'] ?? '0.5rem';\n  cssVars['--persona-radius-xl'] = cssVars['--persona-palette-radius-xl'] ?? '0.75rem';\n  cssVars['--persona-radius-full'] = cssVars['--persona-palette-radius-full'] ?? '9999px';\n  cssVars['--persona-launcher-radius'] =\n    cssVars['--persona-components-launcher-borderRadius'] ??\n    cssVars['--persona-palette-radius-full'] ??\n    '9999px';\n  cssVars['--persona-launcher-bg'] =\n    cssVars['--persona-components-launcher-background'] ??\n    cssVars['--persona-primary'];\n  cssVars['--persona-launcher-fg'] =\n    cssVars['--persona-components-launcher-foreground'] ??\n    cssVars['--persona-text-inverse'];\n  cssVars['--persona-launcher-border'] =\n    cssVars['--persona-components-launcher-border'] ??\n    cssVars['--persona-border'];\n  cssVars['--persona-button-primary-bg'] =\n    cssVars['--persona-components-button-primary-background'] ??\n    cssVars['--persona-primary'];\n  cssVars['--persona-button-primary-fg'] =\n    cssVars['--persona-components-button-primary-foreground'] ??\n    cssVars['--persona-text-inverse'];\n  cssVars['--persona-button-radius'] =\n    cssVars['--persona-components-button-primary-borderRadius'] ??\n    cssVars['--persona-palette-radius-full'] ??\n    '9999px';\n  cssVars['--persona-panel-radius'] =\n    cssVars['--persona-components-panel-borderRadius'] ??\n    cssVars['--persona-radius-xl'] ??\n    '0.75rem';\n  cssVars['--persona-panel-border'] =\n    cssVars['--persona-components-panel-border'] ?? `1px solid ${cssVars['--persona-border']}`;\n  cssVars['--persona-panel-shadow'] =\n    cssVars['--persona-components-panel-shadow'] ??\n    cssVars['--persona-palette-shadows-xl'] ??\n    '0 25px 50px -12px rgba(0, 0, 0, 0.25)';\n  cssVars['--persona-panel-inset'] =\n    cssVars['--persona-components-panel-inset'] ?? DEFAULT_PANEL_INSET;\n  cssVars['--persona-panel-canvas-bg'] =\n    cssVars['--persona-components-panel-canvasBackground'] ?? DEFAULT_PANEL_CANVAS_BACKGROUND;\n  cssVars['--persona-launcher-shadow'] =\n    cssVars['--persona-components-launcher-shadow'] ??\n    '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)';\n  cssVars['--persona-input-radius'] =\n    cssVars['--persona-components-input-borderRadius'] ??\n    cssVars['--persona-radius-lg'] ??\n    '0.5rem';\n  cssVars['--persona-message-user-radius'] =\n    cssVars['--persona-components-message-user-borderRadius'] ??\n    cssVars['--persona-radius-lg'] ??\n    '0.5rem';\n  cssVars['--persona-message-assistant-radius'] =\n    cssVars['--persona-components-message-assistant-borderRadius'] ??\n    cssVars['--persona-radius-lg'] ??\n    '0.5rem';\n\n  // Component-level color overrides: these map component tokens to\n  // dedicated CSS variables that the widget CSS reads for individual elements.\n  cssVars['--persona-header-bg'] =\n    cssVars['--persona-components-header-background'] ?? cssVars['--persona-surface'];\n  cssVars['--persona-header-border'] =\n    cssVars['--persona-components-header-border'] ?? cssVars['--persona-divider'];\n  cssVars['--persona-header-icon-bg'] =\n    cssVars['--persona-components-header-iconBackground'] ?? cssVars['--persona-primary'];\n  cssVars['--persona-header-icon-fg'] =\n    cssVars['--persona-components-header-iconForeground'] ?? cssVars['--persona-text-inverse'];\n  cssVars['--persona-header-title-fg'] =\n    cssVars['--persona-components-header-titleForeground'] ?? cssVars['--persona-primary'];\n  cssVars['--persona-header-subtitle-fg'] =\n    cssVars['--persona-components-header-subtitleForeground'] ?? cssVars['--persona-text-muted'];\n  cssVars['--persona-header-action-icon-fg'] =\n    cssVars['--persona-components-header-actionIconForeground'] ?? cssVars['--persona-muted'];\n\n  const headerTokens = theme.components?.header;\n  if (headerTokens?.shadow) cssVars['--persona-header-shadow'] = headerTokens.shadow;\n  if (headerTokens?.borderBottom) cssVars['--persona-header-border-bottom'] = headerTokens.borderBottom;\n\n  // Intro card aliases: short names the panel inline-styles read directly.\n  // The full-path `--persona-components-introCard-*` variables auto-emit above;\n  // these mirror them with sensible fallbacks so existing pages keep their look.\n  const introCardTokens = theme.components?.introCard;\n  cssVars['--persona-intro-card-bg'] =\n    cssVars['--persona-components-introCard-background'] ?? cssVars['--persona-surface'];\n  cssVars['--persona-intro-card-radius'] =\n    cssVars['--persona-components-introCard-borderRadius'] ?? '1rem';\n  cssVars['--persona-intro-card-padding'] =\n    cssVars['--persona-components-introCard-padding'] ?? '1.5rem';\n  cssVars['--persona-intro-card-shadow'] =\n    introCardTokens?.shadow\n      ?? cssVars['--persona-components-introCard-shadow']\n      ?? '0 5px 15px rgba(15, 23, 42, 0.08)';\n\n  cssVars['--persona-input-background'] =\n    cssVars['--persona-components-input-background'] ?? cssVars['--persona-surface'];\n  cssVars['--persona-input-placeholder'] =\n    cssVars['--persona-components-input-placeholder'] ?? cssVars['--persona-text-muted'];\n\n  cssVars['--persona-message-user-bg'] =\n    cssVars['--persona-components-message-user-background'] ?? cssVars['--persona-accent'];\n  cssVars['--persona-message-user-text'] =\n    cssVars['--persona-components-message-user-text'] ?? cssVars['--persona-text-inverse'];\n  cssVars['--persona-message-user-shadow'] =\n    cssVars['--persona-components-message-user-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';\n  cssVars['--persona-message-assistant-bg'] =\n    cssVars['--persona-components-message-assistant-background'] ?? cssVars['--persona-surface'];\n  cssVars['--persona-message-assistant-text'] =\n    cssVars['--persona-components-message-assistant-text'] ?? cssVars['--persona-text'];\n  cssVars['--persona-message-assistant-border'] =\n    cssVars['--persona-components-message-assistant-border'] ?? cssVars['--persona-border'];\n  cssVars['--persona-message-assistant-shadow'] =\n    cssVars['--persona-components-message-assistant-shadow'] ?? '0 1px 2px 0 rgb(0 0 0 / 0.05)';\n  cssVars['--persona-scroll-to-bottom-bg'] =\n    cssVars['--persona-components-scrollToBottom-background'] ??\n    cssVars['--persona-button-primary-bg'] ??\n    cssVars['--persona-accent'];\n  cssVars['--persona-scroll-to-bottom-fg'] =\n    cssVars['--persona-components-scrollToBottom-foreground'] ??\n    cssVars['--persona-button-primary-fg'] ??\n    cssVars['--persona-text-inverse'];\n  cssVars['--persona-scroll-to-bottom-border'] =\n    cssVars['--persona-components-scrollToBottom-border'] ??\n    cssVars['--persona-primary'];\n  cssVars['--persona-scroll-to-bottom-size'] =\n    cssVars['--persona-components-scrollToBottom-size'] ??\n    '40px';\n  cssVars['--persona-scroll-to-bottom-radius'] =\n    cssVars['--persona-components-scrollToBottom-borderRadius'] ??\n    cssVars['--persona-button-radius'] ??\n    cssVars['--persona-radius-full'] ??\n    '9999px';\n  cssVars['--persona-scroll-to-bottom-shadow'] =\n    cssVars['--persona-components-scrollToBottom-shadow'] ??\n    cssVars['--persona-palette-shadows-sm'] ??\n    '0 1px 2px 0 rgb(0 0 0 / 0.05)';\n  cssVars['--persona-scroll-to-bottom-padding'] =\n    cssVars['--persona-components-scrollToBottom-padding'] ??\n    '0.5rem 0.875rem';\n  cssVars['--persona-scroll-to-bottom-gap'] =\n    cssVars['--persona-components-scrollToBottom-gap'] ??\n    '0.5rem';\n  cssVars['--persona-scroll-to-bottom-font-size'] =\n    cssVars['--persona-components-scrollToBottom-fontSize'] ??\n    cssVars['--persona-palette-typography-fontSize-sm'] ??\n    '0.875rem';\n  cssVars['--persona-scroll-to-bottom-icon-size'] =\n    cssVars['--persona-components-scrollToBottom-iconSize'] ??\n    '14px';\n\n  cssVars['--persona-tool-bubble-shadow'] =\n    cssVars['--persona-components-toolBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';\n  cssVars['--persona-reasoning-bubble-shadow'] =\n    cssVars['--persona-components-reasoningBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';\n  cssVars['--persona-composer-shadow'] =\n    cssVars['--persona-components-composer-shadow'] ?? 'none';\n\n  cssVars['--persona-md-inline-code-bg'] =\n    cssVars['--persona-components-markdown-inlineCode-background'] ?? cssVars['--persona-container'];\n  cssVars['--persona-md-inline-code-color'] =\n    cssVars['--persona-components-markdown-inlineCode-foreground'] ?? cssVars['--persona-text'];\n\n  cssVars['--persona-md-link-color'] =\n    cssVars['--persona-components-markdown-link-foreground'] ??\n    cssVars['--persona-accent'] ??\n    '#0f0f0f';\n\n  const mdH1Size = cssVars['--persona-components-markdown-heading-h1-fontSize'];\n  if (mdH1Size) cssVars['--persona-md-h1-size'] = mdH1Size;\n  const mdH1Weight = cssVars['--persona-components-markdown-heading-h1-fontWeight'];\n  if (mdH1Weight) cssVars['--persona-md-h1-weight'] = mdH1Weight;\n  const mdH2Size = cssVars['--persona-components-markdown-heading-h2-fontSize'];\n  if (mdH2Size) cssVars['--persona-md-h2-size'] = mdH2Size;\n  const mdH2Weight = cssVars['--persona-components-markdown-heading-h2-fontWeight'];\n  if (mdH2Weight) cssVars['--persona-md-h2-weight'] = mdH2Weight;\n\n  const mdProseFont = cssVars['--persona-components-markdown-prose-fontFamily'];\n  if (mdProseFont && mdProseFont !== 'inherit') {\n    cssVars['--persona-md-prose-font-family'] = mdProseFont;\n  }\n\n  // Markdown code block\n  cssVars['--persona-md-code-block-bg'] =\n    cssVars['--persona-components-markdown-codeBlock-background'] ?? cssVars['--persona-container'];\n  cssVars['--persona-md-code-block-border-color'] =\n    cssVars['--persona-components-markdown-codeBlock-borderColor'] ?? cssVars['--persona-border'];\n  cssVars['--persona-md-code-block-text-color'] =\n    cssVars['--persona-components-markdown-codeBlock-textColor'] ?? 'inherit';\n\n  // Markdown table\n  cssVars['--persona-md-table-header-bg'] =\n    cssVars['--persona-components-markdown-table-headerBackground'] ?? cssVars['--persona-container'];\n  cssVars['--persona-md-table-border-color'] =\n    cssVars['--persona-components-markdown-table-borderColor'] ?? cssVars['--persona-border'];\n\n  // Markdown HR\n  cssVars['--persona-md-hr-color'] =\n    cssVars['--persona-components-markdown-hr-color'] ?? cssVars['--persona-divider'];\n\n  // Markdown blockquote\n  cssVars['--persona-md-blockquote-border-color'] =\n    cssVars['--persona-components-markdown-blockquote-borderColor'] ??\n    cssVars['--persona-palette-colors-gray-900'];\n  cssVars['--persona-md-blockquote-bg'] =\n    cssVars['--persona-components-markdown-blockquote-background'] ?? 'transparent';\n  cssVars['--persona-md-blockquote-text-color'] =\n    cssVars['--persona-components-markdown-blockquote-textColor'] ??\n    cssVars['--persona-palette-colors-gray-500'];\n\n  // Collapsible widget chrome (tool/reasoning/approval bubbles)\n  cssVars['--cw-container'] =\n    cssVars['--persona-components-collapsibleWidget-container'] ?? cssVars['--persona-surface'];\n  cssVars['--cw-surface'] =\n    cssVars['--persona-components-collapsibleWidget-surface'] ?? cssVars['--persona-surface'];\n  cssVars['--cw-border'] =\n    cssVars['--persona-components-collapsibleWidget-border'] ?? cssVars['--persona-border'];\n\n  // Message border\n  cssVars['--persona-message-border'] =\n    cssVars['--persona-components-message-border'] ?? cssVars['--persona-border'];\n\n  // Icon button tokens\n  const components = theme.components;\n  const iconBtn = components?.iconButton;\n  if (iconBtn) {\n    if (iconBtn.background) cssVars['--persona-icon-btn-bg'] = iconBtn.background;\n    if (iconBtn.border) cssVars['--persona-icon-btn-border'] = iconBtn.border;\n    if (iconBtn.color) cssVars['--persona-icon-btn-color'] = iconBtn.color;\n    if (iconBtn.padding) cssVars['--persona-icon-btn-padding'] = iconBtn.padding;\n    if (iconBtn.borderRadius) cssVars['--persona-icon-btn-radius'] = iconBtn.borderRadius;\n    if (iconBtn.hoverBackground) cssVars['--persona-icon-btn-hover-bg'] = iconBtn.hoverBackground;\n    if (iconBtn.hoverColor) cssVars['--persona-icon-btn-hover-color'] = iconBtn.hoverColor;\n    if (iconBtn.activeBackground) cssVars['--persona-icon-btn-active-bg'] = iconBtn.activeBackground;\n    if (iconBtn.activeBorder) cssVars['--persona-icon-btn-active-border'] = iconBtn.activeBorder;\n  }\n\n  // Label button tokens\n  const labelBtn = components?.labelButton;\n  if (labelBtn) {\n    if (labelBtn.background) cssVars['--persona-label-btn-bg'] = labelBtn.background;\n    if (labelBtn.border) cssVars['--persona-label-btn-border'] = labelBtn.border;\n    if (labelBtn.color) cssVars['--persona-label-btn-color'] = labelBtn.color;\n    if (labelBtn.padding) cssVars['--persona-label-btn-padding'] = labelBtn.padding;\n    if (labelBtn.borderRadius) cssVars['--persona-label-btn-radius'] = labelBtn.borderRadius;\n    if (labelBtn.hoverBackground) cssVars['--persona-label-btn-hover-bg'] = labelBtn.hoverBackground;\n    if (labelBtn.fontSize) cssVars['--persona-label-btn-font-size'] = labelBtn.fontSize;\n    if (labelBtn.gap) cssVars['--persona-label-btn-gap'] = labelBtn.gap;\n  }\n\n  // Toggle group tokens\n  const toggleGrp = components?.toggleGroup;\n  if (toggleGrp) {\n    if (toggleGrp.gap) cssVars['--persona-toggle-group-gap'] = toggleGrp.gap;\n    if (toggleGrp.borderRadius) cssVars['--persona-toggle-group-radius'] = toggleGrp.borderRadius;\n  }\n\n  // Artifact tokens\n  const artifact = components?.artifact;\n  if (artifact?.toolbar) {\n    const t = artifact.toolbar;\n    if (t.iconHoverColor) cssVars['--persona-artifact-toolbar-icon-hover-color'] = t.iconHoverColor;\n    if (t.iconHoverBackground) cssVars['--persona-artifact-toolbar-icon-hover-bg'] = t.iconHoverBackground;\n    if (t.iconPadding) cssVars['--persona-artifact-toolbar-icon-padding'] = t.iconPadding;\n    if (t.iconBorderRadius) cssVars['--persona-artifact-toolbar-icon-radius'] = t.iconBorderRadius;\n    if (t.iconBorder) cssVars['--persona-artifact-toolbar-icon-border'] = t.iconBorder;\n    if (t.toggleGroupGap) cssVars['--persona-artifact-toolbar-toggle-group-gap'] = t.toggleGroupGap;\n    if (t.toggleBorderRadius) cssVars['--persona-artifact-toolbar-toggle-radius'] = t.toggleBorderRadius;\n    if (t.toggleGroupPadding) cssVars['--persona-artifact-toolbar-toggle-group-padding'] = t.toggleGroupPadding;\n    if (t.toggleGroupBorder) cssVars['--persona-artifact-toolbar-toggle-group-border'] = t.toggleGroupBorder;\n    if (t.toggleGroupBorderRadius) cssVars['--persona-artifact-toolbar-toggle-group-radius'] = t.toggleGroupBorderRadius;\n    if (t.toggleGroupBackground) cssVars['--persona-artifact-toolbar-toggle-group-bg'] = resolveTokenValue(theme, t.toggleGroupBackground) ?? t.toggleGroupBackground;\n    if (t.copyBackground) cssVars['--persona-artifact-toolbar-copy-bg'] = t.copyBackground;\n    if (t.copyBorder) cssVars['--persona-artifact-toolbar-copy-border'] = t.copyBorder;\n    if (t.copyColor) cssVars['--persona-artifact-toolbar-copy-color'] = t.copyColor;\n    if (t.copyBorderRadius) cssVars['--persona-artifact-toolbar-copy-radius'] = t.copyBorderRadius;\n    if (t.copyPadding) cssVars['--persona-artifact-toolbar-copy-padding'] = t.copyPadding;\n    if (t.copyMenuBackground) {\n      cssVars['--persona-artifact-toolbar-copy-menu-bg'] = t.copyMenuBackground;\n      cssVars['--persona-dropdown-bg'] = cssVars['--persona-dropdown-bg'] ?? t.copyMenuBackground;\n    }\n    if (t.copyMenuBorder) {\n      cssVars['--persona-artifact-toolbar-copy-menu-border'] = t.copyMenuBorder;\n      cssVars['--persona-dropdown-border'] = cssVars['--persona-dropdown-border'] ?? t.copyMenuBorder;\n    }\n    if (t.copyMenuShadow) {\n      cssVars['--persona-artifact-toolbar-copy-menu-shadow'] = t.copyMenuShadow;\n      cssVars['--persona-dropdown-shadow'] = cssVars['--persona-dropdown-shadow'] ?? t.copyMenuShadow;\n    }\n    if (t.copyMenuBorderRadius) {\n      cssVars['--persona-artifact-toolbar-copy-menu-radius'] = t.copyMenuBorderRadius;\n      cssVars['--persona-dropdown-radius'] = cssVars['--persona-dropdown-radius'] ?? t.copyMenuBorderRadius;\n    }\n    if (t.copyMenuItemHoverBackground) {\n      cssVars['--persona-artifact-toolbar-copy-menu-item-hover-bg'] = t.copyMenuItemHoverBackground;\n      cssVars['--persona-dropdown-item-hover-bg'] = cssVars['--persona-dropdown-item-hover-bg'] ?? t.copyMenuItemHoverBackground;\n    }\n    if (t.iconBackground) cssVars['--persona-artifact-toolbar-icon-bg'] = t.iconBackground;\n    if (t.toolbarBorder) cssVars['--persona-artifact-toolbar-border'] = t.toolbarBorder;\n  }\n  if (artifact?.tab) {\n    const t = artifact.tab;\n    if (t.background) cssVars['--persona-artifact-tab-bg'] = t.background;\n    if (t.activeBackground) cssVars['--persona-artifact-tab-active-bg'] = t.activeBackground;\n    if (t.activeBorder) cssVars['--persona-artifact-tab-active-border'] = t.activeBorder;\n    if (t.borderRadius) cssVars['--persona-artifact-tab-radius'] = t.borderRadius;\n    if (t.textColor) cssVars['--persona-artifact-tab-color'] = t.textColor;\n    if (t.hoverBackground) cssVars['--persona-artifact-tab-hover-bg'] = t.hoverBackground;\n    if (t.listBackground) cssVars['--persona-artifact-tab-list-bg'] = t.listBackground;\n    if (t.listBorderColor) cssVars['--persona-artifact-tab-list-border-color'] = t.listBorderColor;\n    if (t.listPadding) cssVars['--persona-artifact-tab-list-padding'] = t.listPadding;\n  }\n  if (artifact?.pane) {\n    const t = artifact.pane;\n    if (t.toolbarBackground) {\n      const toolbarBg =\n        resolveTokenValue(theme, t.toolbarBackground) ?? t.toolbarBackground;\n      cssVars['--persona-artifact-toolbar-bg'] = toolbarBg;\n    }\n  }\n  if (artifact?.card) {\n    const t = artifact.card;\n    if (t.background) cssVars['--persona-artifact-card-bg'] = t.background;\n    if (t.border) cssVars['--persona-artifact-card-border'] = t.border;\n    if (t.borderRadius) cssVars['--persona-artifact-card-radius'] = t.borderRadius;\n    if (t.hoverBackground) cssVars['--persona-artifact-card-hover-bg'] = t.hoverBackground;\n    if (t.hoverBorderColor) cssVars['--persona-artifact-card-hover-border'] = t.hoverBorderColor;\n  }\n  if (artifact?.inline) {\n    const t = artifact.inline;\n    if (t.background) {\n      cssVars['--persona-artifact-inline-bg'] =\n        resolveTokenValue(theme, t.background) ?? t.background;\n    }\n    if (t.border) cssVars['--persona-artifact-inline-border'] = t.border;\n    if (t.borderRadius) cssVars['--persona-artifact-inline-radius'] = t.borderRadius;\n    if (t.chromeBackground) {\n      cssVars['--persona-artifact-inline-chrome-bg'] =\n        resolveTokenValue(theme, t.chromeBackground) ?? t.chromeBackground;\n    }\n    if (t.chromeBorder) {\n      cssVars['--persona-artifact-inline-chrome-border'] =\n        resolveTokenValue(theme, t.chromeBorder) ?? t.chromeBorder;\n    }\n    if (t.titleColor) {\n      cssVars['--persona-artifact-inline-title-color'] =\n        resolveTokenValue(theme, t.titleColor) ?? t.titleColor;\n    }\n    if (t.mutedColor) {\n      cssVars['--persona-artifact-inline-muted-color'] =\n        resolveTokenValue(theme, t.mutedColor) ?? t.mutedColor;\n    }\n    if (t.frameHeight) cssVars['--persona-artifact-inline-frame-height'] = t.frameHeight;\n  }\n\n  // Code (syntax-highlighted artifact source view) tokens.\n  const code = components?.code;\n  if (code) {\n    if (code.keywordColor) cssVars['--persona-code-keyword-color'] = code.keywordColor;\n    if (code.stringColor) cssVars['--persona-code-string-color'] = code.stringColor;\n    if (code.commentColor) cssVars['--persona-code-comment-color'] = code.commentColor;\n    if (code.numberColor) cssVars['--persona-code-number-color'] = code.numberColor;\n    if (code.tagColor) cssVars['--persona-code-tag-color'] = code.tagColor;\n    if (code.attrColor) cssVars['--persona-code-attr-color'] = code.attrColor;\n    if (code.propertyColor) cssVars['--persona-code-property-color'] = code.propertyColor;\n    if (code.lineNumberColor) cssVars['--persona-code-line-number-color'] = code.lineNumberColor;\n    if (code.gutterBorderColor) cssVars['--persona-code-gutter-border-color'] = code.gutterBorderColor;\n    if (code.background)\n      cssVars['--persona-code-bg'] = resolveTokenValue(theme, code.background) ?? code.background;\n  }\n\n  // Interactive-state defaults. The default preset resolves container === surface,\n  // which turns every hover/active rule that falls back to --persona-container\n  // into a visual no-op; anchor those states one gray step down in that case.\n  // Component config emitted above must keep winning, so only fill vars not set.\n  const stateSurface = cssVars['--persona-surface'];\n  const stateContainer = cssVars['--persona-container'];\n  const gray100 = cssVars['--persona-palette-colors-gray-100'] ?? '#f3f4f6';\n  const gray200 = cssVars['--persona-palette-colors-gray-200'] ?? '#e5e7eb';\n  const gray300 = cssVars['--persona-palette-colors-gray-300'] ?? '#d1d5db';\n  const flatTheme = !stateContainer || stateContainer === stateSurface;\n  const hoverBgDefault = flatTheme ? gray100 : stateContainer;\n  const activeBgDefault = flatTheme ? gray200 : stateContainer;\n  cssVars['--persona-icon-btn-hover-bg'] = cssVars['--persona-icon-btn-hover-bg'] ?? hoverBgDefault;\n  cssVars['--persona-icon-btn-active-bg'] = cssVars['--persona-icon-btn-active-bg'] ?? activeBgDefault;\n  if (flatTheme) {\n    cssVars['--persona-icon-btn-active-border'] =\n      cssVars['--persona-icon-btn-active-border'] ?? gray300;\n  }\n  cssVars['--persona-label-btn-hover-bg'] = cssVars['--persona-label-btn-hover-bg'] ?? hoverBgDefault;\n  cssVars['--persona-artifact-tab-hover-bg'] =\n    cssVars['--persona-artifact-tab-hover-bg'] ?? hoverBgDefault;\n  cssVars['--persona-artifact-card-hover-bg'] =\n    cssVars['--persona-artifact-card-hover-bg'] ?? hoverBgDefault;\n\n  return cssVars;\n}\n\nexport function applyThemeVariables(element: HTMLElement, theme: PersonaTheme): void {\n  const cssVars = themeToCssVariables(theme);\n\n  for (const [name, value] of Object.entries(cssVars)) {\n    element.style.setProperty(name, value);\n  }\n}\n\n/**\n * Stable `data-persona-theme-zone` values applied to key widget regions.\n * Visual editors should use `[data-persona-theme-zone=\"header\"]` selectors\n * rather than internal class names.\n */\nexport const THEME_ZONES = {\n  header: 'Widget header bar',\n  messages: 'Message list area',\n  'user-message': 'User message bubble',\n  'assistant-message': 'Assistant message bubble',\n  composer: 'Footer / composer area',\n  container: 'Main widget container',\n  'artifact-pane': 'Artifact sidebar',\n  'artifact-toolbar': 'Artifact toolbar',\n  'artifact-inline': 'Inline artifact block',\n  'artifact-inline-chrome': 'Inline artifact title bar',\n} as const;\n\nexport type ThemeZone = keyof typeof THEME_ZONES;\n","import type { DeepPartial, PersonaTheme } from '../types/theme';\nimport type { AgentWidgetConfig } from '../types';\nimport { createTheme, resolveTokens, themeToCssVariables } from './tokens';\nimport { deepMerge } from './deep-merge';\n\nexport type ColorScheme = 'light' | 'dark' | 'auto';\n\nexport interface PersonaWidgetConfig {\n  theme?: DeepPartial<PersonaTheme>;\n  darkTheme?: DeepPartial<PersonaTheme>;\n  colorScheme?: ColorScheme;\n}\n\ntype WidgetConfig = PersonaWidgetConfig | AgentWidgetConfig;\n\nconst DARK_PALETTE = {\n  colors: {\n    primary: {\n      50: '#ffffff',\n      100: '#f5f5f5',\n      200: '#d4d4d4',\n      300: '#a3a3a3',\n      400: '#737373',\n      500: '#171717',\n      600: '#0f0f0f',\n      700: '#0a0a0a',\n      800: '#050505',\n      900: '#030303',\n      950: '#000000',\n    },\n    secondary: {\n      50: '#f5f3ff',\n      100: '#ede9fe',\n      200: '#ddd6fe',\n      300: '#c4b5fd',\n      400: '#a78bfa',\n      500: '#8b5cf6',\n      600: '#7c3aed',\n      700: '#6d28d9',\n      800: '#5b21b6',\n      900: '#4c1d95',\n      950: '#2e1065',\n    },\n    accent: {\n      50: '#ecfeff',\n      100: '#cffafe',\n      200: '#a5f3fc',\n      300: '#67e8f9',\n      400: '#22d3ee',\n      500: '#06b6d4',\n      600: '#0891b2',\n      700: '#0e7490',\n      800: '#155e75',\n      900: '#164e63',\n      950: '#083344',\n    },\n    gray: {\n      50: '#f9fafb',\n      100: '#f3f4f6',\n      200: '#e5e7eb',\n      300: '#d1d5db',\n      400: '#9ca3af',\n      500: '#6b7280',\n      600: '#4b5563',\n      700: '#374151',\n      800: '#1f2937',\n      900: '#111827',\n      950: '#030712',\n    },\n    success: {\n      50: '#f0fdf4',\n      100: '#dcfce7',\n      200: '#bbf7d0',\n      300: '#86efac',\n      400: '#4ade80',\n      500: '#22c55e',\n      600: '#16a34a',\n      700: '#15803d',\n      800: '#166534',\n      900: '#14532d',\n    },\n    warning: {\n      50: '#fefce8',\n      100: '#fef9c3',\n      200: '#fef08a',\n      300: '#fde047',\n      400: '#facc15',\n      500: '#eab308',\n      600: '#ca8a04',\n      700: '#a16207',\n      800: '#854d0e',\n      900: '#713f12',\n    },\n    error: {\n      50: '#fef2f2',\n      100: '#fee2e2',\n      200: '#fecaca',\n      300: '#fca5a5',\n      400: '#f87171',\n      500: '#ef4444',\n      600: '#dc2626',\n      700: '#b91c1c',\n      800: '#991b1b',\n      900: '#7f1d1d',\n    },\n  },\n};\n\n/**\n * Normalize theme config for merging; rejects non-objects.\n */\nconst normalizeThemeConfig = (\n  theme: DeepPartial<PersonaTheme> | Record<string, unknown> | undefined\n): DeepPartial<PersonaTheme> | undefined => {\n  if (!theme || typeof theme !== 'object' || Array.isArray(theme)) return undefined;\n  return theme as DeepPartial<PersonaTheme>;\n};\n\nexport const detectColorScheme = (): 'light' | 'dark' => {\n  if (typeof document !== 'undefined' && document.documentElement.classList.contains('dark')) {\n    return 'dark';\n  }\n\n  if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches) {\n    return 'dark';\n  }\n\n  return 'light';\n};\n\nconst getColorSchemeFromConfig = (config?: WidgetConfig): 'light' | 'dark' => {\n  const colorScheme = config?.colorScheme ?? 'light';\n\n  if (colorScheme === 'light') return 'light';\n  if (colorScheme === 'dark') return 'dark';\n\n  return detectColorScheme();\n};\n\nexport const getColorScheme = (config?: WidgetConfig): 'light' | 'dark' => {\n  return getColorSchemeFromConfig(config);\n};\n\nexport const createLightTheme = (userConfig?: DeepPartial<PersonaTheme>): PersonaTheme => {\n  return createTheme(userConfig);\n};\n\nexport const createDarkTheme = (userConfig?: DeepPartial<PersonaTheme>): PersonaTheme => {\n  const baseTheme = createTheme(undefined, { validate: false });\n  \n  return createTheme(\n    {\n      ...userConfig,\n      palette: {\n        ...baseTheme.palette,\n        colors: {\n          ...DARK_PALETTE.colors,\n          ...userConfig?.palette?.colors,\n        },\n      },\n    },\n    { validate: false }\n  );\n};\n\nexport const getActiveTheme = (config?: WidgetConfig): PersonaTheme => {\n  const scheme = getColorScheme(config);\n  const lightThemeConfig = normalizeThemeConfig(config?.theme);\n  const darkThemeConfig = normalizeThemeConfig(config?.darkTheme);\n\n  if (scheme === 'dark') {\n    return createDarkTheme(\n      deepMerge(\n        (lightThemeConfig ?? {}) as Record<string, unknown>,\n        (darkThemeConfig ?? {}) as Record<string, unknown>\n      ) as DeepPartial<PersonaTheme>\n    );\n  }\n\n  return createLightTheme(lightThemeConfig);\n};\n\nexport const getCssVariables = (theme: PersonaTheme): Record<string, string> => {\n  return themeToCssVariables(theme);\n};\n\nexport const applyThemeVariables = (\n  element: HTMLElement,\n  config?: WidgetConfig\n): void => {\n  const theme = getActiveTheme(config);\n  const cssVars = getCssVariables(theme);\n\n  for (const [name, value] of Object.entries(cssVars)) {\n    element.style.setProperty(name, value);\n  }\n\n  // Stamp the resolved scheme so stylesheet rules can key scheme-specific\n  // defaults (e.g. the syntax-highlight palette) off the widget's own color\n  // scheme instead of the OS prefers-color-scheme, which diverges whenever a\n  // host pins colorScheme. Re-applied on every theme application, so live\n  // config updates and theme-observer callbacks keep it current.\n  element.setAttribute(\"data-persona-color-scheme\", getColorScheme(config));\n};\n\nexport const createThemeObserver = (\n  callback: (scheme: 'light' | 'dark') => void\n): (() => void) => {\n  const cleanupFns: Array<() => void> = [];\n\n  if (typeof document !== 'undefined' && typeof MutationObserver !== 'undefined') {\n    const observer = new MutationObserver(() => {\n      callback(detectColorScheme());\n    });\n\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: ['class'],\n    });\n\n    cleanupFns.push(() => observer.disconnect());\n  }\n\n  if (typeof window !== 'undefined' && window.matchMedia) {\n    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n    const handleChange = () => callback(detectColorScheme());\n\n    if (mediaQuery.addEventListener) {\n      mediaQuery.addEventListener('change', handleChange);\n      cleanupFns.push(() => mediaQuery.removeEventListener('change', handleChange));\n    } else if (mediaQuery.addListener) {\n      mediaQuery.addListener(handleChange);\n      cleanupFns.push(() => mediaQuery.removeListener(handleChange));\n    }\n  }\n\n  return () => {\n    cleanupFns.forEach((fn) => fn());\n  };\n};\n\nexport { createTheme, resolveTokens, themeToCssVariables };\n","/**\n * Helpers for previewable file artifacts.\n *\n * When a Claude Managed agent writes a file, the core backend encodes it as a\n * markdown artifact whose content is a fenced code block (extension → fence\n * language) plus a `file` metadata field on `artifact_start`. The content stays\n * fenced on the wire for backward compatibility; these helpers recover the raw\n * source and classify the file for preview / download.\n */\nimport type { PersonaArtifactFileMeta } from \"../types\";\n\nconst ZERO_WIDTH_SPACE = \"​\";\n\n/** basename of a POSIX or Windows style path. */\nexport function basenameOf(path: string): string {\n  const parts = String(path).split(/[\\\\/]/);\n  return parts[parts.length - 1] ?? \"\";\n}\n\n/** lowercase file extension (without the dot), or \"\" when there is none. */\nfunction extensionOf(path: string): string {\n  const base = basenameOf(path);\n  const dot = base.lastIndexOf(\".\");\n  // dot at index 0 (dotfile) or no dot means \"no extension\"\n  if (dot <= 0) return \"\";\n  return base.slice(dot + 1).toLowerCase();\n}\n\n/**\n * Recover raw file source from core's fenced wire content.\n *\n * The producer builds content as `` ```${lang}\\n${escaped}\\n``` `` where every\n * literal triple-backtick in the source is replaced with backtick + U+200B\n * (zero-width space) + backtick backtick. So the content is exactly\n * `firstline + \"\\n\" + escapedSource + \"\\n\" + lastline`; dropping the first and\n * last lines yields the escaped source, then the ZWSP escape is reversed.\n *\n * Graceful: if `markdown` does not start with a fence line, it is returned\n * unchanged.\n */\nexport function extractFileSource(markdown: string): string {\n  if (typeof markdown !== \"string\") return \"\";\n  const firstNl = markdown.indexOf(\"\\n\");\n  if (firstNl === -1) return markdown;\n  const firstLine = markdown.slice(0, firstNl);\n  if (!firstLine.startsWith(\"```\")) return markdown;\n  const rest = markdown.slice(firstNl + 1);\n  const lastNl = rest.lastIndexOf(\"\\n\");\n  const escaped = lastNl === -1 ? rest : rest.slice(0, lastNl);\n  // Reverse core's fence-terminator escape (backtick + ZWSP + backtick backtick).\n  return escaped.split(\"`\" + ZERO_WIDTH_SPACE + \"``\").join(\"```\");\n}\n\n/** Coarse file kind used to pick a preview strategy. */\nexport function fileKindOf(\n  meta: PersonaArtifactFileMeta\n): \"html\" | \"svg\" | \"markdown\" | \"other\" {\n  const ext = extensionOf(meta.path);\n  if (ext) {\n    if (ext === \"html\" || ext === \"htm\") return \"html\";\n    if (ext === \"svg\") return \"svg\";\n    if (ext === \"md\" || ext === \"mdx\") return \"markdown\";\n    return \"other\";\n  }\n  const mime = (meta.mimeType || \"\").toLowerCase();\n  if (mime.includes(\"html\")) return \"html\";\n  if (mime.includes(\"svg\")) return \"svg\";\n  if (mime.includes(\"markdown\")) return \"markdown\";\n  return \"other\";\n}\n\n/** Human-readable file type label for card subtitles / pane titles. */\nexport function fileTypeLabel(meta: PersonaArtifactFileMeta): string {\n  switch (fileKindOf(meta)) {\n    case \"html\":\n      return \"HTML\";\n    case \"svg\":\n      return \"SVG\";\n    case \"markdown\":\n      return \"Markdown\";\n    default: {\n      const ext = extensionOf(meta.path);\n      return ext ? ext.toUpperCase() : \"File\";\n    }\n  }\n}\n\n/** Minimal record shape needed to compute download info. */\nexport interface ArtifactDownloadSource {\n  title?: string;\n  markdown?: string;\n  file?: PersonaArtifactFileMeta;\n}\n\n/**\n * Resolve the download filename, MIME type, and content for an artifact.\n *\n * File artifacts download the raw unfenced source under their basename with\n * their real MIME type. Non-file markdown artifacts preserve the legacy\n * behavior exactly: `<title>.md`, `text/markdown`, raw fenced/markdown content.\n */\nexport function downloadInfoFor(record: ArtifactDownloadSource): {\n  filename: string;\n  mime: string;\n  content: string;\n} {\n  const raw = record.markdown ?? \"\";\n  if (record.file) {\n    return {\n      filename: basenameOf(record.file.path) || \"artifact\",\n      mime: record.file.mimeType || \"application/octet-stream\",\n      content: extractFileSource(raw),\n    };\n  }\n  const title = record.title || \"artifact\";\n  return { filename: `${title}.md`, mime: \"text/markdown\", content: raw };\n}\n","import { cx } from \"./dom\";\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/**\n * Reusable icon spinner: a small SVG ring with a faint full track plus a\n * rotating arc.\n *\n * Icon-first loading is the norm for preview surfaces (Sandpack, YouTube/Figma/\n * CodePen embeds, ChatGPT/Claude/v0) and every major design system (Apple HIG,\n * Material, Carbon, Polaris, Geist); text-only \"Loading…\" is used by none, and\n * HIG explicitly warns against the vague word \"loading\". So the default preview\n * indicator is this spinner, with any work-naming label added only later as an\n * escalation. The arc spins via a GPU-friendly `transform: rotate` keyframe\n * (see `.persona-spinner` in widget.css); under `prefers-reduced-motion` the\n * rotation stops and the static arc still reads as a progress ring.\n *\n * Structure and animation live in CSS (class `persona-spinner`); geometry here\n * is viewBox-relative so the rendered size follows `--persona-artifact-spinner-size`.\n */\nexport function createSpinner(className?: string): SVGSVGElement {\n  const svg = document.createElementNS(SVG_NS, \"svg\");\n  svg.setAttribute(\"class\", cx(\"persona-spinner\", className));\n  svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n  // Decorative: the loading state is announced by surrounding copy/labels, so\n  // the spinner itself must not add redundant noise to the accessibility tree.\n  svg.setAttribute(\"aria-hidden\", \"true\");\n  svg.setAttribute(\"focusable\", \"false\");\n\n  const track = document.createElementNS(SVG_NS, \"circle\");\n  track.setAttribute(\"class\", \"persona-spinner-track\");\n  track.setAttribute(\"cx\", \"12\");\n  track.setAttribute(\"cy\", \"12\");\n  track.setAttribute(\"r\", \"9\");\n\n  const arc = document.createElementNS(SVG_NS, \"circle\");\n  arc.setAttribute(\"class\", \"persona-spinner-arc\");\n  arc.setAttribute(\"cx\", \"12\");\n  arc.setAttribute(\"cy\", \"12\");\n  arc.setAttribute(\"r\", \"9\");\n\n  svg.appendChild(track);\n  svg.appendChild(arc);\n  return svg;\n}\n","import { createElement } from \"./dom\";\n\n/**\n * Append text as individual animated character spans.\n * Each character becomes a `persona-tool-char` span carrying a `--char-index`\n * custom property so the shimmer/rainbow keyframes can stagger their delay.\n * Spaces are rendered as non-breaking spaces so they animate like any other\n * character. Returns the next available character index.\n */\nexport const appendCharSpans = (\n  container: HTMLElement,\n  text: string,\n  startIndex: number\n): number => {\n  let idx = startIndex;\n  for (const char of text) {\n    const span = createElement(\"span\", \"persona-tool-char\");\n    span.style.setProperty(\"--char-index\", String(idx));\n    span.textContent = char === \" \" ? \"\\u00A0\" : char;\n    container.appendChild(span);\n    idx++;\n  }\n  return idx;\n};\n","import type { AgentWidgetArtifactsFeature } from \"../types\";\nimport { appendCharSpans } from \"./tool-loading-animation\";\n\n/**\n * Apply the artifact \"Generating …\" streaming animation to a status element.\n *\n * Shared by the artifact reference card and the inline chrome so both surfaces\n * honor `features.artifacts.loadingAnimation` (and the color / duration knobs)\n * identically. Character-by-character modes (`shimmer`, `shimmer-color`,\n * `rainbow`) wrap each character in a `persona-tool-char` span; `pulse` fades\n * the whole element; `none` renders plain text.\n *\n * The caller owns the element and its reset — pass a fresh (or cleared) span so\n * stale animation classes from a previous streaming render do not linger.\n */\nexport function applyArtifactLoadingStatus(\n  statusText: HTMLElement,\n  text: string,\n  artifactsCfg: AgentWidgetArtifactsFeature | undefined\n): void {\n  const loadingAnimation = artifactsCfg?.loadingAnimation ?? \"shimmer\";\n  const duration = artifactsCfg?.loadingAnimationDuration ?? 2000;\n\n  if (loadingAnimation === \"none\") {\n    statusText.textContent = text;\n    return;\n  }\n\n  if (loadingAnimation === \"pulse\") {\n    statusText.setAttribute(\"data-preserve-animation\", \"true\");\n    statusText.classList.add(\"persona-tool-loading-pulse\");\n    statusText.style.setProperty(\"--persona-tool-anim-duration\", `${duration}ms`);\n    statusText.textContent = text;\n    return;\n  }\n\n  statusText.setAttribute(\"data-preserve-animation\", \"true\");\n  statusText.classList.add(`persona-tool-loading-${loadingAnimation}`);\n  statusText.style.setProperty(\"--persona-tool-anim-duration\", `${duration}ms`);\n  if (loadingAnimation === \"shimmer-color\") {\n    if (artifactsCfg?.loadingAnimationColor) {\n      statusText.style.setProperty(\n        \"--persona-tool-anim-color\",\n        artifactsCfg.loadingAnimationColor\n      );\n    }\n    if (artifactsCfg?.loadingAnimationSecondaryColor) {\n      statusText.style.setProperty(\n        \"--persona-tool-anim-secondary-color\",\n        artifactsCfg.loadingAnimationSecondaryColor\n      );\n    }\n  }\n  appendCharSpans(statusText, text, 0);\n}\n","import type {\n  AgentWidgetArtifactsFeature,\n  PersonaArtifactRecord,\n  PersonaArtifactStatusLabelContext\n} from \"../types\";\nimport { createElement } from \"./dom\";\nimport { fileTypeLabel } from \"./artifact-file\";\nimport { applyArtifactLoadingStatus } from \"./artifact-loading-status\";\n\n/**\n * Shared resolver + applier for the artifact streaming status label\n * (`features.artifacts.statusLabel`).\n *\n * Three surfaces render the \"Generating …\" status while an artifact streams:\n * the reference card status line, the inline chrome meta span, and the inline\n * streaming status body. All three resolve their text through\n * {@link resolveArtifactStatusLabel} and paint it through\n * {@link applyArtifactStatus}, so a host `statusLabel` string / function reads\n * identically everywhere.\n *\n * Animation-stability contract: the animated label span is (re)built only when\n * its text changes, so a per-delta detail update (live counters) never restarts\n * the shimmer/pulse/typewriter animation. `applyArtifactStatus` enforces this by\n * tracking the last-applied label text on the label span and by keeping the\n * plain detail span as a separate sibling the animation never touches.\n */\n\nexport type ArtifactStatusSurface = \"card\" | \"inline-chrome\" | \"status-body\";\n\nexport type ResolvedArtifactStatus = { label: string; detail?: string };\n\n/** Class on the animated label span managed inside a status container. */\nexport const STATUS_LABEL_CLASS = \"persona-artifact-status-label\";\n/** Class on the plain, freely-updating detail span. */\nexport const STATUS_DETAIL_CLASS = \"persona-artifact-status-detail\";\n/** Attribute recording the last label text applied to the label span. */\nconst APPLIED_LABEL_ATTR = \"data-artifact-status-label\";\n\n/**\n * First-seen streaming timestamp per artifact id, so `elapsedMs` starts near 0\n * and a completed → re-streamed artifact restarts cleanly. Set on the first\n * streaming resolve; cleared on complete (see {@link clearArtifactStatusTracking})\n * so the map cannot grow without bound.\n */\nconst firstSeenAt = new Map<string, number>();\n\nconst now = (): number =>\n  typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n    ? performance.now()\n    : Date.now();\n\n/** Drop the elapsed-time tracking for an artifact. Call on complete. */\nexport function clearArtifactStatusTracking(id: string): void {\n  if (id) firstSeenAt.delete(id);\n}\n\n/** Type subject used to build the default label; mirrors the card/chrome logic. */\nfunction typeLabelFor(record: PersonaArtifactRecord): string {\n  const file = record.artifactType === \"markdown\" ? record.file : undefined;\n  if (file) return fileTypeLabel(file);\n  return record.artifactType === \"component\" ? \"Component\" : \"Document\";\n}\n\n/**\n * Resolve the streaming status label + optional detail for one surface.\n *\n * - `statusLabel` unset → the default `Generating <type>...`.\n * - string → that string as the label (no detail).\n * - function → called with a per-surface context; a string return is the label,\n *   an object return is `{ label, detail }`. A throw falls back to the default.\n */\nexport function resolveArtifactStatusLabel(\n  record: PersonaArtifactRecord,\n  cfg: AgentWidgetArtifactsFeature | undefined,\n  surface: ArtifactStatusSurface\n): ResolvedArtifactStatus {\n  const typeLabel = typeLabelFor(record);\n  const defaultLabel = `Generating ${typeLabel.toLowerCase()}...`;\n\n  const statusLabel = cfg?.statusLabel;\n  if (typeof statusLabel === \"string\") {\n    return { label: statusLabel };\n  }\n  if (typeof statusLabel !== \"function\") {\n    return { label: defaultLabel };\n  }\n\n  // Track first-seen only while streaming (the surfaces only resolve while\n  // streaming); elapsedMs measures from that instant.\n  const id = record.id;\n  let firstSeen = firstSeenAt.get(id);\n  if (record.status !== \"complete\" && firstSeen === undefined) {\n    firstSeen = now();\n    firstSeenAt.set(id, firstSeen);\n  }\n  const elapsedMs = firstSeen === undefined ? 0 : Math.max(0, now() - firstSeen);\n\n  const isComponent = record.artifactType === \"component\";\n  const markdown =\n    !isComponent && typeof record.markdown === \"string\" ? record.markdown : \"\";\n  const chars = isComponent ? 0 : markdown.length;\n  const lines = isComponent || markdown === \"\" ? 0 : markdown.split(\"\\n\").length;\n  const file = record.artifactType === \"markdown\" ? record.file : undefined;\n\n  const ctx: PersonaArtifactStatusLabelContext = {\n    artifactId: id,\n    artifactType: record.artifactType,\n    title: record.title,\n    typeLabel,\n    file,\n    chars,\n    lines,\n    elapsedMs,\n    // Lazy: only materialize the accumulated source if the host asks for it.\n    content: () => (isComponent ? \"\" : markdown),\n    surface\n  };\n\n  try {\n    const result = statusLabel(ctx);\n    if (typeof result === \"string\") return { label: result };\n    if (\n      result &&\n      typeof result === \"object\" &&\n      typeof result.label === \"string\"\n    ) {\n      return {\n        label: result.label,\n        detail: typeof result.detail === \"string\" ? result.detail : undefined\n      };\n    }\n    // A malformed return (null/number/etc.) falls back like a throw.\n    return { label: defaultLabel };\n  } catch {\n    // A bad host callback must never break rendering.\n    return { label: defaultLabel };\n  }\n}\n\n/** Restore the label span to its base state before re-applying the animation. */\nfunction resetLabelSpan(labelEl: HTMLElement): void {\n  labelEl.className = STATUS_LABEL_CLASS;\n  labelEl.removeAttribute(\"data-preserve-animation\");\n  labelEl.style.removeProperty(\"--persona-tool-anim-duration\");\n  labelEl.style.removeProperty(\"--persona-tool-anim-color\");\n  labelEl.style.removeProperty(\"--persona-tool-anim-secondary-color\");\n  labelEl.replaceChildren();\n}\n\n/**\n * Paint a resolved status into `container`, which owns exactly two children: an\n * animated label span and an optional plain detail span.\n *\n * The label span is rebuilt (and its animation re-applied) ONLY when its text\n * differs from the last application, so a detail-only update never restarts the\n * loading animation. The detail span updates its `textContent` freely and is\n * removed when the resolved detail is empty.\n */\nexport function applyArtifactStatus(\n  container: HTMLElement,\n  resolved: ResolvedArtifactStatus,\n  cfg: AgentWidgetArtifactsFeature | undefined\n): void {\n  let labelEl = container.querySelector<HTMLElement>(\n    `:scope > .${STATUS_LABEL_CLASS}`\n  );\n  if (!labelEl) {\n    labelEl = createElement(\"span\", STATUS_LABEL_CLASS);\n    container.appendChild(labelEl);\n  }\n  if (labelEl.getAttribute(APPLIED_LABEL_ATTR) !== resolved.label) {\n    resetLabelSpan(labelEl);\n    applyArtifactLoadingStatus(labelEl, resolved.label, cfg);\n    labelEl.setAttribute(APPLIED_LABEL_ATTR, resolved.label);\n  }\n\n  let detailEl = container.querySelector<HTMLElement>(\n    `:scope > .${STATUS_DETAIL_CLASS}`\n  );\n  const detail = resolved.detail;\n  if (detail) {\n    if (!detailEl) {\n      detailEl = createElement(\"span\", STATUS_DETAIL_CLASS);\n      container.appendChild(detailEl);\n    }\n    if (detailEl.textContent !== detail) detailEl.textContent = detail;\n  } else if (detailEl) {\n    detailEl.remove();\n  }\n}\n","/**\n * Tiny hand-rolled syntax highlighter for the artifact source view.\n *\n * Zero dependencies by design: the IIFE bundle is built with `--splitting\n * false`, so any highlight.js / prism / shiki style library would be inlined\n * and inflate the critical bundle. This module is a small regex tokenizer\n * (html / css / js|ts / json) plus a DOM builder that emits one\n * `span.persona-code-line` per source line for the CSS-counter gutter.\n *\n * Safety by construction: the DOM is built ONLY with `createElement` +\n * `textContent` + `createTextNode` — never `innerHTML` — so the output is safe\n * regardless of the widget's `sanitize` config, even when the source itself is\n * hostile markup (e.g. `<img src=x onerror=…>`).\n *\n * Verbatim-reconstruction invariant: the built fragment's `textContent` equals\n * the original `source` exactly. Every source newline becomes a literal `\"\\n\"`\n * text node placed *between* line spans; token spans never contain a newline\n * (multi-line tokens such as block comments and template literals are split at\n * newline boundaries into per-line spans of the same class). A trailing source\n * newline is preserved as a `\"\\n\"` text node without a phantom numbered empty\n * final line.\n */\nimport { createElement, createFragment } from \"./dom\";\n\n/** Token classification. `plain`/`punctuation` render as bare text (no span). */\nexport type CodeTokenType =\n  | \"keyword\"\n  | \"string\"\n  | \"comment\"\n  | \"number\"\n  | \"tag\"\n  | \"attr\"\n  | \"property\"\n  | \"punctuation\"\n  | \"plain\";\n\nexport interface CodeToken {\n  type: CodeTokenType;\n  value: string;\n}\n\n/** Canonical tokenizer keys. `ts`/`jsx`/`tsx` share the `js` tokenizer. */\nexport type HighlightLanguage = \"html\" | \"css\" | \"js\" | \"json\";\n\n/** Token type → CSS class. Types absent here render as a bare text node. */\nconst TOKEN_CLASS: Partial<Record<CodeTokenType, string>> = {\n  keyword: \"persona-code-token-keyword\",\n  string: \"persona-code-token-string\",\n  comment: \"persona-code-token-comment\",\n  number: \"persona-code-token-number\",\n  tag: \"persona-code-token-tag\",\n  attr: \"persona-code-token-attr\",\n  property: \"persona-code-token-property\",\n};\n\n/**\n * Streaming perf guard: skip tokenization above this many characters and fall\n * back to plain line-numbered text. `renderPre` runs on every streaming delta,\n * so keep the tokenizer off huge sources.\n */\nexport const MAX_HIGHLIGHT_LENGTH = 150_000;\n\n/** Extension / short-name → canonical tokenizer key. */\nconst LANGUAGE_ALIASES: Record<string, HighlightLanguage> = {\n  html: \"html\",\n  htm: \"html\",\n  xhtml: \"html\",\n  xml: \"html\",\n  svg: \"html\",\n  css: \"css\",\n  js: \"js\",\n  jsx: \"js\",\n  mjs: \"js\",\n  cjs: \"js\",\n  ts: \"js\",\n  tsx: \"js\",\n  mts: \"js\",\n  cts: \"js\",\n  javascript: \"js\",\n  typescript: \"js\",\n  ecmascript: \"js\",\n  json: \"json\",\n  jsonc: \"json\",\n  json5: \"json\",\n};\n\n/**\n * Resolve a canonical tokenizer from an explicit language or a file path\n * extension. Returns `null` for anything unmapped (plain-text fallback).\n */\nexport function resolveHighlightLanguage(\n  language?: string,\n  path?: string\n): HighlightLanguage | null {\n  const lang = (language || \"\").trim().toLowerCase();\n  if (lang && LANGUAGE_ALIASES[lang]) return LANGUAGE_ALIASES[lang];\n\n  if (path) {\n    const dot = path.lastIndexOf(\".\");\n    if (dot >= 0) {\n      const ext = path.slice(dot + 1).toLowerCase();\n      if (LANGUAGE_ALIASES[ext]) return LANGUAGE_ALIASES[ext];\n    }\n  }\n  return null;\n}\n\ninterface Rule {\n  type: CodeTokenType;\n  /** Sticky (`y`) regex so a match only counts when it starts at `lastIndex`. */\n  re: RegExp;\n  /** Optional reclassifier (e.g. identifier → keyword when in a keyword set). */\n  map?: (text: string) => CodeTokenType;\n}\n\n/**\n * Left-to-right rule scanner. At each index the first rule that matches at that\n * exact position wins; unmatched characters accrete into a single `plain` run.\n */\nfunction runRules(source: string, rules: Rule[]): CodeToken[] {\n  const tokens: CodeToken[] = [];\n  let i = 0;\n  let plainStart = 0;\n  const flushPlain = (end: number) => {\n    if (end > plainStart) {\n      tokens.push({ type: \"plain\", value: source.slice(plainStart, end) });\n    }\n  };\n\n  while (i < source.length) {\n    let matched = false;\n    for (const rule of rules) {\n      rule.re.lastIndex = i;\n      const m = rule.re.exec(source);\n      if (m && m.index === i && m[0].length > 0) {\n        flushPlain(i);\n        const type = rule.map ? rule.map(m[0]) : rule.type;\n        tokens.push({ type, value: m[0] });\n        i += m[0].length;\n        plainStart = i;\n        matched = true;\n        break;\n      }\n    }\n    if (!matched) i += 1;\n  }\n  flushPlain(source.length);\n  return tokens;\n}\n\nconst JS_KEYWORDS = new Set([\n  \"abstract\", \"any\", \"as\", \"async\", \"await\", \"boolean\", \"break\", \"case\",\n  \"catch\", \"class\", \"const\", \"continue\", \"debugger\", \"declare\", \"default\",\n  \"delete\", \"do\", \"else\", \"enum\", \"export\", \"extends\", \"false\", \"finally\",\n  \"for\", \"from\", \"function\", \"get\", \"if\", \"implements\", \"import\", \"in\",\n  \"instanceof\", \"interface\", \"keyof\", \"let\", \"namespace\", \"never\", \"new\",\n  \"null\", \"number\", \"object\", \"of\", \"private\", \"protected\", \"public\",\n  \"readonly\", \"return\", \"satisfies\", \"set\", \"static\", \"string\", \"super\",\n  \"switch\", \"symbol\", \"this\", \"throw\", \"true\", \"try\", \"type\", \"typeof\",\n  \"undefined\", \"unknown\", \"var\", \"void\", \"while\", \"yield\",\n]);\n\nconst JS_RULES: Rule[] = [\n  { type: \"comment\", re: /\\/\\/[^\\n]*/y },\n  { type: \"comment\", re: /\\/\\*[\\s\\S]*?\\*\\//y },\n  { type: \"string\", re: /`(?:\\\\[\\s\\S]|[^\\\\`])*`/y },\n  { type: \"string\", re: /\"(?:\\\\.|[^\"\\\\\\n])*\"/y },\n  { type: \"string\", re: /'(?:\\\\.|[^'\\\\\\n])*'/y },\n  {\n    type: \"number\",\n    re: /0[xX][0-9a-fA-F]+|(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/y,\n  },\n  {\n    type: \"plain\",\n    re: /[A-Za-z_$][\\w$]*/y,\n    map: (t) => (JS_KEYWORDS.has(t) ? \"keyword\" : \"plain\"),\n  },\n];\n\nfunction tokenizeJs(source: string): CodeToken[] {\n  return runRules(source, JS_RULES);\n}\n\nconst JSON_RULES: Rule[] = [\n  { type: \"string\", re: /\"(?:\\\\.|[^\"\\\\])*\"/y },\n  { type: \"number\", re: /-?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/y },\n  {\n    type: \"plain\",\n    re: /[A-Za-z_]\\w*/y,\n    map: (t) =>\n      t === \"true\" || t === \"false\" || t === \"null\" ? \"keyword\" : \"plain\",\n  },\n];\n\nfunction tokenizeJson(source: string): CodeToken[] {\n  const tokens = runRules(source, JSON_RULES);\n  // Reclassify object keys: a string whose next significant token opens with\n  // `:` is a property, not a value string.\n  for (let idx = 0; idx < tokens.length; idx += 1) {\n    if (tokens[idx].type !== \"string\") continue;\n    let j = idx + 1;\n    while (j < tokens.length && tokens[j].value.trim() === \"\") j += 1;\n    const next = tokens[j];\n    if (next && next.value.replace(/^\\s*/, \"\").startsWith(\":\")) {\n      tokens[idx].type = \"property\";\n    }\n  }\n  return tokens;\n}\n\nconst CSS_RULES: Rule[] = [\n  { type: \"comment\", re: /\\/\\*[\\s\\S]*?\\*\\//y },\n  { type: \"string\", re: /\"(?:\\\\.|[^\"\\\\\\n])*\"/y },\n  { type: \"string\", re: /'(?:\\\\.|[^'\\\\\\n])*'/y },\n  { type: \"keyword\", re: /@[A-Za-z-]+/y },\n  { type: \"number\", re: /#[0-9a-fA-F]{3,8}\\b/y },\n  { type: \"number\", re: /-?(?:\\d+\\.?\\d*|\\.\\d+)(?:[a-z%]+)?/y },\n  { type: \"plain\", re: /[A-Za-z_-][\\w-]*/y },\n];\n\nfunction tokenizeCss(source: string): CodeToken[] {\n  const tokens = runRules(source, CSS_RULES);\n  // Property detection: an identifier immediately followed by `:` counts as a\n  // declaration property only when the previous significant character opens a\n  // declaration (`{`, `}`, `;`, `,`, or start of input). This keeps selectors\n  // and pseudo-classes (`a:hover`) plain in the common cases.\n  let lastSignificant = \"\";\n  for (let idx = 0; idx < tokens.length; idx += 1) {\n    const tok = tokens[idx];\n    if (tok.type === \"plain\" && /^[A-Za-z_-][\\w-]*$/.test(tok.value)) {\n      let j = idx + 1;\n      while (j < tokens.length && tokens[j].value.trim() === \"\") j += 1;\n      const next = tokens[j];\n      if (\n        next &&\n        next.value.replace(/^\\s*/, \"\").startsWith(\":\") &&\n        (lastSignificant === \"\" ||\n          lastSignificant === \"{\" ||\n          lastSignificant === \"}\" ||\n          lastSignificant === \";\" ||\n          lastSignificant === \",\")\n      ) {\n        tok.type = \"property\";\n      }\n    }\n    const trimmed = tok.value.replace(/\\s+$/, \"\");\n    if (trimmed) lastSignificant = trimmed[trimmed.length - 1];\n  }\n  return tokens;\n}\n\nconst HTML_TAG_RULES: Rule[] = [\n  { type: \"tag\", re: /<\\/?\\s*[A-Za-z][\\w:-]*/y },\n  { type: \"string\", re: /\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'/y },\n  { type: \"attr\", re: /[A-Za-z_:@][\\w:.-]*(?=\\s*=)/y },\n  { type: \"attr\", re: /[A-Za-z_:@][\\w:.-]*/y },\n  { type: \"tag\", re: /\\/?>/y },\n];\n\nfunction tokenizeHtml(source: string): CodeToken[] {\n  const tokens: CodeToken[] = [];\n  const push = (type: CodeTokenType, value: string) => {\n    if (value) tokens.push({ type, value });\n  };\n  const n = source.length;\n  let i = 0;\n\n  while (i < n) {\n    // Comments.\n    if (source.startsWith(\"<!--\", i)) {\n      const end = source.indexOf(\"-->\", i + 4);\n      const stop = end === -1 ? n : end + 3;\n      push(\"comment\", source.slice(i, stop));\n      i = stop;\n      continue;\n    }\n    // Doctype (and other `<!…>` declarations).\n    if (source[i] === \"<\" && source[i + 1] === \"!\") {\n      const end = source.indexOf(\">\", i);\n      const stop = end === -1 ? n : end + 1;\n      push(\"tag\", source.slice(i, stop));\n      i = stop;\n      continue;\n    }\n    // Element tag.\n    if (source[i] === \"<\" && /[A-Za-z/]/.test(source[i + 1] || \"\")) {\n      const tagEnd = source.indexOf(\">\", i);\n      const stop = tagEnd === -1 ? n : tagEnd + 1;\n      const tagText = source.slice(i, stop);\n      for (const t of runRules(tagText, HTML_TAG_RULES)) tokens.push(t);\n      i = stop;\n\n      // Embedded <script>/<style> bodies: tokenize with the js/css tokenizer.\n      const embed = /^<\\s*(script|style)\\b/i.exec(tagText);\n      if (embed && !/\\/>\\s*$/.test(tagText)) {\n        const tag = embed[1].toLowerCase();\n        const closeRe = new RegExp(\"</\\\\s*\" + tag + \"\\\\s*>\", \"i\");\n        const cm = closeRe.exec(source.slice(i));\n        const innerEnd = cm ? i + cm.index : n;\n        const inner = source.slice(i, innerEnd);\n        const innerTokens =\n          tag === \"script\" ? tokenizeJs(inner) : tokenizeCss(inner);\n        for (const t of innerTokens) tokens.push(t);\n        i = innerEnd;\n      }\n      continue;\n    }\n    // Text content up to the next `<` (or a lone `<` that is not a tag).\n    const lt = source.indexOf(\"<\", i);\n    if (lt === i) {\n      push(\"plain\", source[i]);\n      i += 1;\n    } else {\n      const stop = lt === -1 ? n : lt;\n      push(\"plain\", source.slice(i, stop));\n      i = stop;\n    }\n  }\n  return tokens;\n}\n\nfunction tokenizeByLanguage(\n  source: string,\n  lang: HighlightLanguage\n): CodeToken[] {\n  switch (lang) {\n    case \"html\":\n      return tokenizeHtml(source);\n    case \"css\":\n      return tokenizeCss(source);\n    case \"js\":\n      return tokenizeJs(source);\n    case \"json\":\n      return tokenizeJson(source);\n  }\n}\n\n/** Build one `span.persona-code-line` per source line from a token stream. */\nfunction buildLines(tokens: CodeToken[]): DocumentFragment {\n  const frag = createFragment();\n  let line = createElement(\"span\", \"persona-code-line\");\n\n  const appendSegment = (type: CodeTokenType, text: string) => {\n    if (!text) return;\n    const cls = TOKEN_CLASS[type];\n    if (cls) {\n      const span = createElement(\"span\", cls);\n      span.textContent = text;\n      line.appendChild(span);\n    } else {\n      line.appendChild(document.createTextNode(text));\n    }\n  };\n\n  for (const tok of tokens) {\n    const parts = tok.value.split(\"\\n\");\n    for (let p = 0; p < parts.length; p += 1) {\n      if (p > 0) {\n        // Close the current line, then emit the literal newline separator that\n        // keeps `textContent` verbatim. `.persona-code` sets white-space:normal\n        // so this whitespace-only node collapses visually between block lines.\n        frag.appendChild(line);\n        frag.appendChild(document.createTextNode(\"\\n\"));\n        line = createElement(\"span\", \"persona-code-line\");\n      }\n      appendSegment(tok.type, parts[p]);\n    }\n  }\n\n  if (line.childNodes.length > 0) {\n    frag.appendChild(line);\n  } else if (!frag.lastChild) {\n    // Wholly empty source: keep the single empty line so the gutter shows \"1\".\n    frag.appendChild(line);\n  }\n  // Otherwise the source ended with a newline: the trailing \"\\n\" separator is\n  // already in the fragment, so drop the empty phantom final line + number.\n  return frag;\n}\n\n/**\n * Tokenize `source` and return a DocumentFragment of line spans (+ newline\n * separator text nodes). Prefers the explicit `language`, then the `path`\n * extension, else plain text. Sources longer than {@link MAX_HIGHLIGHT_LENGTH}\n * are returned as plain line-numbered text (still gutter-numbered, no colors).\n */\nexport function highlightCode(\n  source: string,\n  language?: string,\n  path?: string\n): DocumentFragment {\n  const lang =\n    source.length <= MAX_HIGHLIGHT_LENGTH\n      ? resolveHighlightLanguage(language, path)\n      : null;\n  const tokens: CodeToken[] = lang\n    ? tokenizeByLanguage(source, lang)\n    : [{ type: \"plain\", value: source }];\n  return buildLines(tokens);\n}\n","import { AgentWidgetSessionStatus } from \"../session\";\n\nexport const statusCopy: Record<AgentWidgetSessionStatus, string> = {\n  idle: \"Online\",\n  connecting: \"Connecting…\",\n  connected: \"Streaming…\",\n  error: \"Offline\",\n  paused: \"Connection lost…\",\n  resuming: \"Reconnecting…\"\n};\n\n/**\n * Default z-index for widget overlays. Used for the floating panel, launcher\n * button, sidebar, mobile fullscreen, and docked mobile fullscreen modes.\n * Integrators can override via `launcher.zIndex`.\n */\nexport const DEFAULT_OVERLAY_Z_INDEX = 100000;\n\n/**\n * Z-index for elements portaled to document.body (tooltips, dropdowns).\n * Must be above the widget overlay so portaled UI is not clipped.\n */\nexport const PORTALED_OVERLAY_Z_INDEX = DEFAULT_OVERLAY_Z_INDEX + 1;\n\n\n\n\n\n\n\n\n","import { createElement } from \"./dom\";\nimport { renderLucideIcon } from \"./icons\";\nimport { PORTALED_OVERLAY_Z_INDEX } from \"./constants\";\n\nexport interface DropdownMenuItem {\n  id: string;\n  label: string;\n  /** Lucide icon name to show before the label. */\n  icon?: string;\n  /** When true, item text is styled in a destructive/danger color. */\n  destructive?: boolean;\n  /** When true, a visual divider is inserted before this item. */\n  dividerBefore?: boolean;\n}\n\nexport interface CreateDropdownOptions {\n  /** Menu items to render. */\n  items: DropdownMenuItem[];\n  /** Called when a menu item is selected. */\n  onSelect: (id: string) => void;\n  /** Anchor element used for positioning. When `portal` is not set the menu is appended inside this element (which must have position: relative). */\n  anchor: HTMLElement;\n  /** Alignment of the menu relative to the anchor. Default: 'bottom-left'. */\n  position?: 'bottom-left' | 'bottom-right';\n  /**\n   * Portal target element. When set, the menu is appended to this element\n   * and uses fixed positioning calculated from the anchor's bounding rect.\n   * Use this to escape `overflow: hidden` containers.\n   */\n  portal?: HTMLElement;\n}\n\nexport interface DropdownMenuHandle {\n  /** The menu DOM element. */\n  element: HTMLElement;\n  /** Show the menu. */\n  show: () => void;\n  /** Hide the menu. */\n  hide: () => void;\n  /** Toggle visibility. */\n  toggle: () => void;\n  /** Remove the menu and clean up all listeners. */\n  destroy: () => void;\n}\n\n/**\n * Create a dropdown menu attached to an anchor element.\n *\n * The menu is styled via `.persona-dropdown-menu` CSS rules and themed\n * through `--persona-dropdown-*` CSS variables with semantic fallbacks.\n *\n * ```ts\n * import { createDropdownMenu } from \"@runtypelabs/persona\";\n *\n * const dropdown = createDropdownMenu({\n *   items: [\n *     { id: \"edit\", label: \"Edit\", icon: \"pencil\" },\n *     { id: \"delete\", label: \"Delete\", icon: \"trash-2\", destructive: true, dividerBefore: true },\n *   ],\n *   onSelect: (id) => console.log(\"selected\", id),\n *   anchor: buttonElement,\n * });\n * anchor.appendChild(dropdown.element);\n * button.addEventListener(\"click\", () => dropdown.toggle());\n * ```\n */\nexport function createDropdownMenu(options: CreateDropdownOptions): DropdownMenuHandle {\n  const { items, onSelect, anchor, position = 'bottom-left', portal } = options;\n\n  const menu = createElement(\"div\", \"persona-dropdown-menu persona-hidden\");\n  menu.setAttribute(\"role\", \"menu\");\n  menu.setAttribute(\"data-persona-theme-zone\", \"dropdown\");\n\n  if (portal) {\n    // Fixed positioning: menu is portaled outside the anchor's overflow context\n    menu.style.position = \"fixed\";\n    menu.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);\n  } else {\n    // Absolute positioning: menu lives inside the anchor\n    menu.style.position = \"absolute\";\n    menu.style.top = \"100%\";\n    menu.style.marginTop = \"4px\";\n    if (position === 'bottom-right') {\n      menu.style.right = \"0\";\n    } else {\n      menu.style.left = \"0\";\n    }\n  }\n\n  // Build menu items\n  for (const item of items) {\n    if (item.dividerBefore) {\n      const hr = document.createElement(\"hr\");\n      menu.appendChild(hr);\n    }\n\n    const btn = document.createElement(\"button\");\n    btn.type = \"button\";\n    btn.setAttribute(\"role\", \"menuitem\");\n    btn.setAttribute(\"data-dropdown-item-id\", item.id);\n    if (item.destructive) {\n      btn.setAttribute(\"data-destructive\", \"\");\n    }\n\n    if (item.icon) {\n      const icon = renderLucideIcon(item.icon, 16, \"currentColor\", 1.5);\n      if (icon) btn.appendChild(icon);\n    }\n\n    const labelSpan = document.createElement(\"span\");\n    labelSpan.textContent = item.label;\n    btn.appendChild(labelSpan);\n\n    btn.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      hide();\n      onSelect(item.id);\n    });\n\n    menu.appendChild(btn);\n  }\n\n  let cleanupClickOutside: (() => void) | null = null;\n\n  /** Reposition a portaled menu based on the anchor's current bounding rect. */\n  function reposition() {\n    if (!portal) return;\n    const rect = anchor.getBoundingClientRect();\n    menu.style.top = `${rect.bottom + 4}px`;\n    if (position === 'bottom-right') {\n      menu.style.right = `${window.innerWidth - rect.right}px`;\n      menu.style.left = \"auto\";\n    } else {\n      menu.style.left = `${rect.left}px`;\n      menu.style.right = \"auto\";\n    }\n  }\n\n  function show() {\n    reposition();\n    menu.classList.remove(\"persona-hidden\");\n    // Defer click-outside listener to avoid catching the triggering click\n    requestAnimationFrame(() => {\n      const handler = (e: MouseEvent) => {\n        if (!menu.contains(e.target as Node) && !anchor.contains(e.target as Node)) {\n          hide();\n        }\n      };\n      document.addEventListener(\"click\", handler, true);\n      cleanupClickOutside = () => document.removeEventListener(\"click\", handler, true);\n    });\n  }\n\n  function hide() {\n    menu.classList.add(\"persona-hidden\");\n    cleanupClickOutside?.();\n    cleanupClickOutside = null;\n  }\n\n  function toggle() {\n    if (menu.classList.contains(\"persona-hidden\")) {\n      show();\n    } else {\n      hide();\n    }\n  }\n\n  function destroy() {\n    hide();\n    menu.remove();\n  }\n\n  // Append to portal target or let the caller append manually\n  if (portal) {\n    portal.appendChild(menu);\n  }\n\n  return { element: menu, show, hide, toggle, destroy };\n}\n","import { createElement } from \"./dom\";\nimport { renderLucideIcon } from \"./icons\";\nimport { createDropdownMenu, type DropdownMenuItem } from \"./dropdown\";\n\n// ---------------------------------------------------------------------------\n// createIconButton\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createIconButton}. */\nexport interface CreateIconButtonOptions {\n  /** Lucide icon name (kebab-case, e.g. \"eye\", \"chevron-down\"). */\n  icon: string;\n  /** Accessible label (used for aria-label and title). */\n  label: string;\n  /** Icon size in pixels. Default: 16. */\n  size?: number;\n  /** Icon stroke width. Default: 2. */\n  strokeWidth?: number;\n  /** Extra CSS class(es) appended after \"persona-icon-btn\". */\n  className?: string;\n  /** Click handler. */\n  onClick?: (e: MouseEvent) => void;\n  /** Additional ARIA attributes (e.g. { \"aria-haspopup\": \"true\" }). */\n  aria?: Record<string, string>;\n}\n\n/**\n * Creates a minimal icon-only button with accessible labelling.\n *\n * The button receives the base class `persona-icon-btn` and renders a single\n * Lucide icon inside it.\n */\nexport function createIconButton(options: CreateIconButtonOptions): HTMLButtonElement {\n  const { icon, label, size, strokeWidth, className, onClick, aria } = options;\n\n  const btn = createElement(\n    \"button\",\n    \"persona-icon-btn\" + (className ? \" \" + className : \"\"),\n  );\n  btn.type = \"button\";\n  btn.setAttribute(\"aria-label\", label);\n  btn.title = label;\n\n  const svg = renderLucideIcon(icon, size ?? 16, \"currentColor\", strokeWidth ?? 2);\n  if (svg) {\n    btn.appendChild(svg);\n  }\n\n  if (onClick) {\n    btn.addEventListener(\"click\", onClick);\n  }\n\n  if (aria) {\n    for (const [key, value] of Object.entries(aria)) {\n      btn.setAttribute(key, value);\n    }\n  }\n\n  return btn;\n}\n\n// ---------------------------------------------------------------------------\n// createLabelButton\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createLabelButton}. */\nexport interface CreateLabelButtonOptions {\n  /** Optional Lucide icon name shown before the label. */\n  icon?: string;\n  /** Button text label (also used for aria-label). */\n  label: string;\n  /** Visual variant. Default: \"default\". */\n  variant?: \"default\" | \"primary\" | \"destructive\" | \"ghost\";\n  /** Size preset. Default: \"sm\". */\n  size?: \"sm\" | \"md\";\n  /** Icon size in pixels. Default: 14. */\n  iconSize?: number;\n  /** Extra CSS class(es). */\n  className?: string;\n  /** Click handler. */\n  onClick?: (e: MouseEvent) => void;\n  /** Additional ARIA attributes. */\n  aria?: Record<string, string>;\n}\n\n/**\n * Creates a button with an optional leading icon and a text label.\n *\n * CSS classes follow the BEM-like pattern:\n * `persona-label-btn persona-label-btn--{variant} persona-label-btn--{size}`\n */\nexport function createLabelButton(options: CreateLabelButtonOptions): HTMLButtonElement {\n  const {\n    icon,\n    label,\n    variant = \"default\",\n    size = \"sm\",\n    iconSize,\n    className,\n    onClick,\n    aria,\n  } = options;\n\n  let classString = \"persona-label-btn\";\n  if (variant !== \"default\") {\n    classString += \" persona-label-btn--\" + variant;\n  }\n  classString += \" persona-label-btn--\" + size;\n  if (className) {\n    classString += \" \" + className;\n  }\n\n  const btn = createElement(\"button\", classString);\n  btn.type = \"button\";\n  btn.setAttribute(\"aria-label\", label);\n\n  if (icon) {\n    const svg = renderLucideIcon(icon, iconSize ?? 14, \"currentColor\", 2);\n    if (svg) {\n      btn.appendChild(svg);\n    }\n  }\n\n  const span = createElement(\"span\");\n  span.textContent = label;\n  btn.appendChild(span);\n\n  if (onClick) {\n    btn.addEventListener(\"click\", onClick);\n  }\n\n  if (aria) {\n    for (const [key, value] of Object.entries(aria)) {\n      btn.setAttribute(key, value);\n    }\n  }\n\n  return btn;\n}\n\n// ---------------------------------------------------------------------------\n// createToggleGroup\n// ---------------------------------------------------------------------------\n\n/** Describes a single item inside a toggle group. */\nexport interface ToggleGroupItem {\n  id: string;\n  /** Lucide icon name. If omitted, uses label as text. */\n  icon?: string;\n  /** Accessible label for the button. */\n  label: string;\n  /** Extra CSS class(es) appended after \"persona-icon-btn\". */\n  className?: string;\n}\n\n/** Options for {@link createToggleGroup}. */\nexport interface CreateToggleGroupOptions {\n  /** Toggle items. */\n  items: ToggleGroupItem[];\n  /** Initially selected item id. */\n  selectedId: string;\n  /** Called when selection changes. */\n  onSelect: (id: string) => void;\n  /** Extra CSS class(es) on the wrapper. */\n  className?: string;\n}\n\n/** Handle returned by {@link createToggleGroup}. */\nexport interface ToggleGroupHandle {\n  /** The wrapper element containing toggle buttons. */\n  element: HTMLElement;\n  /** Programmatically change the selected item. */\n  setSelected: (id: string) => void;\n}\n\n/**\n * Creates a group of mutually-exclusive toggle buttons.\n *\n * Each button uses `aria-pressed` to communicate its state. Only one button\n * can be active at a time.\n */\nexport function createToggleGroup(options: CreateToggleGroupOptions): ToggleGroupHandle {\n  const { items, selectedId, onSelect, className } = options;\n\n  const wrapper = createElement(\n    \"div\",\n    \"persona-toggle-group\" + (className ? \" \" + className : \"\"),\n  );\n  wrapper.setAttribute(\"role\", \"group\");\n\n  let currentId = selectedId;\n  const buttons: { id: string; btn: HTMLButtonElement }[] = [];\n\n  function updatePressed() {\n    for (const entry of buttons) {\n      entry.btn.setAttribute(\"aria-pressed\", entry.id === currentId ? \"true\" : \"false\");\n    }\n  }\n\n  for (const item of items) {\n    let btn: HTMLButtonElement;\n\n    if (item.icon) {\n      btn = createIconButton({\n        icon: item.icon,\n        label: item.label,\n        className: item.className,\n        onClick: () => {\n          currentId = item.id;\n          updatePressed();\n          onSelect(item.id);\n        },\n      });\n    } else {\n      btn = createElement(\n        \"button\",\n        \"persona-icon-btn\" + (item.className ? \" \" + item.className : \"\"),\n      );\n      btn.type = \"button\";\n      btn.setAttribute(\"aria-label\", item.label);\n      btn.title = item.label;\n      btn.textContent = item.label;\n      btn.addEventListener(\"click\", () => {\n        currentId = item.id;\n        updatePressed();\n        onSelect(item.id);\n      });\n    }\n\n    btn.setAttribute(\"aria-pressed\", item.id === currentId ? \"true\" : \"false\");\n    buttons.push({ id: item.id, btn });\n    wrapper.appendChild(btn);\n  }\n\n  function setSelected(id: string) {\n    currentId = id;\n    updatePressed();\n  }\n\n  return { element: wrapper, setSelected };\n}\n\n// ---------------------------------------------------------------------------\n// createComboButton\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createComboButton}. */\nexport interface CreateComboButtonOptions {\n  /** Button text label. */\n  label: string;\n  /** Lucide icon name for the dropdown indicator (default: \"chevron-down\"). */\n  icon?: string;\n  /** Dropdown menu items. */\n  menuItems: DropdownMenuItem[];\n  /** Called when a menu item is selected. */\n  onSelect: (id: string) => void;\n  /** Where to align the dropdown. Default: \"bottom-left\". */\n  position?: \"bottom-left\" | \"bottom-right\";\n  /**\n   * Portal target for the dropdown menu. When set, the menu escapes\n   * overflow containers by rendering inside this element with fixed positioning.\n   */\n  portal?: HTMLElement;\n  /** Extra CSS class(es) on the wrapper element. */\n  className?: string;\n  /** Hover style for the pill effect. */\n  hover?: {\n    background?: string;\n    border?: string;\n    borderRadius?: string;\n    padding?: string;\n  };\n}\n\n/** Handle returned by {@link createComboButton}. */\nexport interface ComboButtonHandle {\n  /** The wrapper element (label + chevron + dropdown). */\n  element: HTMLElement;\n  /** Update the displayed label text. */\n  setLabel: (text: string) => void;\n  /** Open the dropdown. */\n  open: () => void;\n  /** Close the dropdown. */\n  close: () => void;\n  /** Toggle the dropdown. */\n  toggle: () => void;\n  /** Remove from DOM and clean up listeners. */\n  destroy: () => void;\n}\n\n/**\n * Creates a combo button: a clickable label with a chevron that opens a dropdown menu.\n *\n * The entire label + chevron area acts as a single interactive unit with an optional\n * hover pill effect. Clicking anywhere on it toggles the dropdown.\n *\n * ```ts\n * import { createComboButton } from \"@runtypelabs/persona\";\n *\n * const combo = createComboButton({\n *   label: \"Chat Assistant\",\n *   menuItems: [\n *     { id: \"star\", label: \"Star\", icon: \"star\" },\n *     { id: \"rename\", label: \"Rename\", icon: \"pencil\" },\n *     { id: \"delete\", label: \"Delete\", icon: \"trash-2\", destructive: true, dividerBefore: true },\n *   ],\n *   onSelect: (id) => console.log(\"Selected:\", id),\n * });\n * header.appendChild(combo.element);\n * ```\n */\nexport function createComboButton(options: CreateComboButtonOptions): ComboButtonHandle {\n  const {\n    label,\n    icon = \"chevron-down\",\n    menuItems,\n    onSelect,\n    position = \"bottom-left\",\n    portal,\n    className,\n    hover,\n  } = options;\n\n  const wrapper = createElement(\n    \"div\",\n    \"persona-combo-btn\" + (className ? \" \" + className : \"\"),\n  );\n  wrapper.style.position = \"relative\";\n  wrapper.style.display = \"inline-flex\";\n  wrapper.style.alignItems = \"center\";\n  wrapper.style.cursor = \"pointer\";\n  wrapper.setAttribute(\"role\", \"button\");\n  wrapper.setAttribute(\"tabindex\", \"0\");\n  wrapper.setAttribute(\"aria-haspopup\", \"true\");\n  wrapper.setAttribute(\"aria-expanded\", \"false\");\n  wrapper.setAttribute(\"aria-label\", label);\n\n  // Label text\n  const labelEl = createElement(\"span\", \"persona-combo-btn-label\");\n  labelEl.textContent = label;\n  wrapper.appendChild(labelEl);\n\n  // Chevron icon\n  const chevron = renderLucideIcon(icon, 14, \"currentColor\", 2);\n  if (chevron) {\n    chevron.style.marginLeft = \"4px\";\n    chevron.style.opacity = \"0.6\";\n    wrapper.appendChild(chevron);\n  }\n\n  // Hover pill effect\n  if (hover) {\n    wrapper.style.borderRadius = hover.borderRadius ?? \"10px\";\n    wrapper.style.padding = hover.padding ?? \"6px 4px 6px 12px\";\n    wrapper.style.border = \"1px solid transparent\";\n    wrapper.style.transition = \"background-color 0.15s ease, border-color 0.15s ease\";\n    wrapper.addEventListener(\"mouseenter\", () => {\n      wrapper.style.backgroundColor = hover.background ?? \"\";\n      wrapper.style.borderColor = hover.border ?? \"\";\n    });\n    wrapper.addEventListener(\"mouseleave\", () => {\n      wrapper.style.backgroundColor = \"\";\n      wrapper.style.borderColor = \"transparent\";\n    });\n  }\n\n  // Dropdown\n  const dropdown = createDropdownMenu({\n    items: menuItems,\n    onSelect: (id) => {\n      wrapper.setAttribute(\"aria-expanded\", \"false\");\n      onSelect(id);\n    },\n    anchor: wrapper,\n    position,\n    portal,\n  });\n\n  if (!portal) {\n    wrapper.appendChild(dropdown.element);\n  }\n\n  // Click toggles dropdown\n  wrapper.addEventListener(\"click\", (e) => {\n    e.stopPropagation();\n    const isOpen = !dropdown.element.classList.contains(\"persona-hidden\");\n    wrapper.setAttribute(\"aria-expanded\", isOpen ? \"false\" : \"true\");\n    dropdown.toggle();\n  });\n\n  // Keyboard support\n  wrapper.addEventListener(\"keydown\", (e) => {\n    if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      wrapper.click();\n    }\n  });\n\n  return {\n    element: wrapper,\n    setLabel: (text: string) => {\n      labelEl.textContent = text;\n      wrapper.setAttribute(\"aria-label\", text);\n    },\n    open: () => {\n      wrapper.setAttribute(\"aria-expanded\", \"true\");\n      dropdown.show();\n    },\n    close: () => {\n      wrapper.setAttribute(\"aria-expanded\", \"false\");\n      dropdown.hide();\n    },\n    toggle: () => {\n      const isOpen = !dropdown.element.classList.contains(\"persona-hidden\");\n      wrapper.setAttribute(\"aria-expanded\", isOpen ? \"false\" : \"true\");\n      dropdown.toggle();\n    },\n    destroy: () => {\n      dropdown.destroy();\n      wrapper.remove();\n    },\n  };\n}\n","import type {\n  PersonaArtifactActionContext,\n  PersonaArtifactCustomAction,\n  PersonaArtifactRecord,\n} from \"../types\";\nimport { createIconButton, createLabelButton } from \"./buttons\";\nimport { createElement } from \"./dom\";\n\n/** Base class applied to every custom artifact action button. */\nconst CUSTOM_ACTION_CLASS = \"persona-artifact-custom-action-btn\";\n\n/**\n * Builds a toolbar/card button for a {@link PersonaArtifactCustomAction}.\n *\n * The result always carries `persona-artifact-custom-action-btn` after the\n * button-system base classes. When `opts.documentChrome` is set, the button\n * also picks up the document-toolbar styling used by the built-in controls.\n *\n * A string `icon` routes through the shared button helpers; a factory `icon`\n * is hand-assembled to match the same class structure so author-owned SVGs\n * slot in without duplicating the button system. A throwing factory falls back\n * to a no-icon button rather than breaking the toolbar.\n *\n * `opts.onClick` (used by the toolbar path) is attached as a click listener;\n * the card path omits it and wires clicks via event delegation instead.\n */\nexport function buildArtifactActionButton(\n  action: PersonaArtifactCustomAction,\n  opts?: { documentChrome?: boolean; onClick?: () => void }\n): HTMLButtonElement {\n  const documentChrome = opts?.documentChrome ?? false;\n\n  let btn: HTMLButtonElement;\n\n  if (typeof action.icon === \"function\") {\n    // Hand-assemble to mirror createIconButton/createLabelButton, with the\n    // author-provided element in place of a Lucide icon.\n    if (action.showLabel) {\n      const className =\n        \"persona-label-btn persona-label-btn--sm \" +\n        CUSTOM_ACTION_CLASS +\n        (documentChrome ? \" persona-artifact-doc-copy-btn\" : \"\");\n      btn = createElement(\"button\", className);\n    } else {\n      const className =\n        \"persona-icon-btn \" +\n        CUSTOM_ACTION_CLASS +\n        (documentChrome ? \" persona-artifact-doc-icon-btn\" : \"\");\n      btn = createElement(\"button\", className);\n    }\n    btn.type = \"button\";\n    btn.setAttribute(\"aria-label\", action.label);\n    btn.title = action.label;\n\n    try {\n      const el = action.icon();\n      if (el) {\n        btn.appendChild(el);\n      }\n    } catch {\n      /* a throwing factory must not break the toolbar; fall back to no icon */\n    }\n\n    if (action.showLabel) {\n      const span = createElement(\"span\");\n      span.textContent = action.label;\n      btn.appendChild(span);\n    }\n  } else if (action.showLabel || !action.icon) {\n    // Visible label, or no icon at all: icon-less actions fall back to a text\n    // button so the control always has a visible affordance (an empty\n    // icon-only button would render as a bare chip and warn on every render).\n    btn = createLabelButton({\n      icon: action.icon,\n      label: action.label,\n      className:\n        CUSTOM_ACTION_CLASS + (documentChrome ? \" persona-artifact-doc-copy-btn\" : \"\"),\n    });\n  } else {\n    // Icon-only: icon is a registry name.\n    btn = createIconButton({\n      icon: action.icon,\n      label: action.label,\n      className:\n        CUSTOM_ACTION_CLASS + (documentChrome ? \" persona-artifact-doc-icon-btn\" : \"\"),\n    });\n  }\n\n  if (opts?.onClick) {\n    btn.addEventListener(\"click\", opts.onClick);\n  }\n\n  return btn;\n}\n\n/**\n * Derives an action context from a persisted artifact record, mirroring how the\n * artifact pane discriminates record shapes. Returns `null` when no record is\n * available so callers can skip evaluating visibility gates / handlers.\n */\nexport function artifactRecordActionContext(\n  record: PersonaArtifactRecord | undefined\n): PersonaArtifactActionContext | null {\n  if (!record) {\n    return null;\n  }\n\n  const ctx: PersonaArtifactActionContext = {\n    artifactId: record.id,\n    title: record.title ?? \"\",\n    artifactType: record.artifactType,\n  };\n\n  if (record.artifactType === \"markdown\") {\n    ctx.markdown = record.markdown ?? \"\";\n    if (record.file) {\n      ctx.file = record.file;\n    }\n  } else {\n    ctx.jsonPayload = JSON.stringify(\n      { component: record.component, props: record.props },\n      null,\n      2\n    );\n  }\n\n  return ctx;\n}\n","import type { ComponentContext, ComponentRenderer } from \"./registry\";\nimport type { PersonaArtifactFileMeta, PersonaArtifactRecord } from \"../types\";\nimport { fileTypeLabel, basenameOf } from \"../utils/artifact-file\";\nimport {\n  applyArtifactStatus,\n  clearArtifactStatusTracking,\n  resolveArtifactStatusLabel\n} from \"../utils/artifact-status-label\";\nimport { createLabelButton } from \"../utils/buttons\";\nimport { buildArtifactActionButton } from \"../utils/artifact-custom-actions\";\nimport type { PersonaArtifactActionContext } from \"../types\";\n\n/**\n * Default artifact card renderer.\n * Builds the compact clickable card shown in the chat thread.\n */\nfunction renderDefaultArtifactCard(\n  props: Record<string, unknown>,\n  context: ComponentContext\n): HTMLElement {\n  const file =\n    props.file && typeof props.file === \"object\" && !Array.isArray(props.file)\n      ? (props.file as PersonaArtifactFileMeta)\n      : undefined;\n  const rawTitle =\n    typeof props.title === \"string\" && props.title\n      ? props.title\n      : \"Untitled artifact\";\n  // File artifacts show the basename (title stays the full path on the wire).\n  const title = file ? basenameOf(file.path) : rawTitle;\n  const artifactId =\n    typeof props.artifactId === \"string\" ? props.artifactId : \"\";\n  const status = props.status === \"streaming\" ? \"streaming\" : \"complete\";\n  const artifactType =\n    typeof props.artifactType === \"string\" ? props.artifactType : \"markdown\";\n  const subtitle = file\n    ? fileTypeLabel(file)\n    : artifactType === \"component\"\n      ? \"Component\"\n      : \"Document\";\n\n  const root = document.createElement(\"div\");\n  root.className =\n    \"persona-artifact-card persona-flex persona-w-full persona-max-w-full persona-items-center persona-gap-3 persona-px-4 persona-py-3\";\n  root.tabIndex = 0;\n  root.setAttribute(\"role\", \"button\");\n  root.setAttribute(\"aria-label\", `Open ${title} in artifact panel`);\n  if (artifactId) {\n    root.setAttribute(\"data-open-artifact\", artifactId);\n  }\n\n  // Document icon\n  const iconBox = document.createElement(\"div\");\n  iconBox.className =\n    \"persona-flex persona-h-10 persona-w-10 persona-flex-shrink-0 persona-items-center persona-justify-center persona-rounded-lg\";\n  iconBox.style.border = \"1px solid var(--persona-border, #e5e7eb)\";\n  iconBox.style.color = \"var(--persona-muted, #9ca3af)\";\n  iconBox.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>`;\n\n  // Title and subtitle\n  const meta = document.createElement(\"div\");\n  meta.className =\n    \"persona-min-w-0 persona-flex-1 persona-flex persona-flex-col persona-gap-0.5\";\n\n  const titleEl = document.createElement(\"div\");\n  titleEl.className = \"persona-truncate persona-text-sm persona-font-medium\";\n  titleEl.style.color = \"var(--persona-text, #1f2937)\";\n  titleEl.textContent = title;\n\n  const subtitleEl = document.createElement(\"div\");\n  subtitleEl.className = \"persona-text-xs persona-flex persona-items-center persona-gap-1.5\";\n  subtitleEl.style.color = \"var(--persona-muted, #9ca3af)\";\n\n  if (status === \"streaming\") {\n    const artifactsCfg = context?.config?.features?.artifacts;\n    // The card re-renders only at streaming-start and completion (deltas flow to\n    // the pane / inline block, not here), so live counters don't tick on the\n    // card; the string / label form still applies. Build a minimal record from\n    // the card props so the shared resolver derives the same typeLabel + ctx.\n    const record: PersonaArtifactRecord = {\n      id: artifactId,\n      artifactType: artifactType === \"component\" ? \"component\" : \"markdown\",\n      title: rawTitle,\n      status: \"streaming\",\n      ...(typeof props.markdown === \"string\" ? { markdown: props.markdown } : {}),\n      ...(file ? { file } : {})\n    };\n    const resolved = resolveArtifactStatusLabel(record, artifactsCfg, \"card\");\n    applyArtifactStatus(subtitleEl, resolved, artifactsCfg);\n  } else {\n    if (artifactId) clearArtifactStatusTracking(artifactId);\n    subtitleEl.textContent = subtitle;\n  }\n\n  meta.append(titleEl, subtitleEl);\n  root.append(iconBox, meta);\n\n  // Custom actions + Download button render only on complete artifacts, same\n  // as Download: content-dependent actions during streaming would act on\n  // partial content, so they wait until the artifact is fully materialized.\n  if (status === \"complete\") {\n    // Custom card actions carry NO direct listeners (the card re-renders during\n    // streaming and is morphed by idiomorph). Clicks are handled by event\n    // delegation in ui.ts, keyed off data-artifact-custom-action, mirroring the\n    // Download button's data-download-artifact delegation.\n    const cardActions = context?.config?.features?.artifacts?.cardActions;\n    if (cardActions && cardActions.length > 0) {\n      const ctx: PersonaArtifactActionContext = {\n        artifactId: artifactId || null,\n        title,\n        artifactType,\n        markdown:\n          typeof props.markdown === \"string\" ? props.markdown : undefined,\n        file,\n      };\n      for (const action of cardActions) {\n        try {\n          if (action.visible === undefined || action.visible(ctx)) {\n            const btn = buildArtifactActionButton(action);\n            btn.setAttribute(\"data-artifact-custom-action\", action.id);\n            btn.className = `${btn.className} persona-flex-shrink-0`;\n            root.append(btn);\n          }\n        } catch {\n          // A single bad action must not take down the whole card.\n        }\n      }\n    }\n\n    const dl = createLabelButton({\n      label: \"Download\",\n      className: \"persona-flex-shrink-0\",\n    });\n    dl.title = `Download ${title}`;\n    dl.setAttribute(\"data-download-artifact\", artifactId);\n    root.append(dl);\n  }\n\n  return root;\n}\n\n/**\n * Built-in artifact reference card component.\n * Renders a compact clickable card in the chat thread that links to an artifact.\n * Uses `data-open-artifact` attribute for click delegation (handled in ui.ts).\n *\n * Supports a custom `renderCard` callback via `config.features.artifacts.renderCard`\n * that can override the default card rendering.\n */\nexport const PersonaArtifactCard: ComponentRenderer = (props, context) => {\n  const customRenderer = context?.config?.features?.artifacts?.renderCard;\n  if (customRenderer) {\n    const title =\n      typeof props.title === \"string\" && props.title\n        ? props.title\n        : \"Untitled artifact\";\n    const artifactId =\n      typeof props.artifactId === \"string\" ? props.artifactId : \"\";\n    const status = props.status === \"streaming\" ? \"streaming\" : \"complete\";\n    const artifactType =\n      typeof props.artifactType === \"string\" ? props.artifactType : \"markdown\";\n\n    const result = customRenderer({\n      artifact: { artifactId, title, artifactType, status },\n      config: context.config,\n      defaultRenderer: () => renderDefaultArtifactCard(props, context),\n    });\n    if (result) return result;\n  }\n\n  return renderDefaultArtifactCard(props, context);\n};\n","import type { ComponentContext, ComponentRenderer } from \"./registry\";\nimport type {\n  AgentWidgetArtifactsFeature,\n  PersonaArtifactFileMeta,\n  PersonaArtifactRecord\n} from \"../types\";\nimport { createElement } from \"../utils/dom\";\nimport { basenameOf, fileKindOf, fileTypeLabel } from \"../utils/artifact-file\";\nimport {\n  applyArtifactStatus,\n  clearArtifactStatusTracking,\n  resolveArtifactStatusLabel\n} from \"../utils/artifact-status-label\";\nimport { artifactRecordActionContext, buildArtifactActionButton } from \"../utils/artifact-custom-actions\";\nimport { createIconButton, createToggleGroup } from \"../utils/buttons\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport {\n  renderArtifactPreviewBody,\n  runArtifactBodyTransition,\n  type ArtifactBodyLayout\n} from \"./artifact-preview\";\nimport { PersonaArtifactCard } from \"./artifact-card\";\n\n/**\n * Built-in inline artifact block component (display: \"inline\").\n *\n * Renders the artifact preview body directly in the chat thread via the\n * shared `renderArtifactPreviewBody` — the same surface the artifact pane\n * uses — inside a file-preview chrome (title bar + toolbar) so an inline block\n * reads like a document, not a naked body in a bordered box.\n *\n * Data flow:\n * - The initial render builds a record from the message props (the same\n *   rawContent JSON-component shape the card uses). While streaming that is\n *   an empty/partial record; after `artifact_complete` the client embeds the\n *   accumulated markdown + file meta in the props, so a refreshed session\n *   re-renders the full preview from props alone (hydration path).\n * - Live streaming updates come from the session artifact registry: the block\n *   registers an updater keyed on its root element, and the UI layer pushes\n *   every `onArtifactsState` emission through `updateInlineArtifactBlocks`,\n *   which routes the matching record into the preview handle's `update()` and\n *   the chrome's in-place `updateChrome()`.\n *\n * The chrome updates in place on the same stable root (pane-style), never a\n * card-style remount — re-attaching the body would reload a live file-preview\n * iframe. While streaming the chrome shows a streaming status and hides copy +\n * custom actions; on complete it swaps the status for the type label and\n * reveals the complete-gated actions. The chrome also carries an optional\n * per-block rendered/source view toggle (availability-gated to file artifacts\n * that have a rendered alternative to their source), which cross-fades the body\n * between the preview and the raw highlighted source.\n *\n * Chrome + built-in actions are configurable via\n * `config.features.artifacts.inlineChrome`\n * (boolean or { showCopy, showExpand, showViewToggle })\n * and `config.features.artifacts.inlineActions`. A full structural override is\n * available via `config.features.artifacts.renderInline` (mirroring the card's\n * `renderCard`), which short-circuits before the default renderer.\n *\n * Body layout is configured via `config.features.artifacts.inlineBody`,\n * resolved here into a flat {@link ArtifactBodyLayout} that is threaded into the\n * shared preview renderer (inline path only — the pane never passes it). This\n * component owns the height model that surrounds that renderer: it sets the\n * `--persona-artifact-inline-body-height` CSS var on the root per state, toggles\n * the complete-state non-iframe height cap on the body wrapper, and wraps the\n * streaming→complete swap in a View Transition when enabled. The default\n * (fixed 320px streaming window, tail-follow, top fade, auto transition) changes\n * the pre-existing grow-with-content streaming behavior; `inlineBody.height:\n * \"auto\"` restores it.\n */\n\nexport type InlineArtifactUpdateOptions = {\n  /**\n   * Skip the streaming→complete View Transition for this update. The View\n   * Transition captures the whole document, so running it while the transcript\n   * is still streaming cross-fades a stale snapshot of the moving message text\n   * over the live text — which reads as ghosting/motion blur on the chat\n   * messages. The body swap still happens, just instantly.\n   */\n  suppressTransition?: boolean;\n};\n\nconst inlineBlockUpdaters = new WeakMap<\n  HTMLElement,\n  (record: PersonaArtifactRecord, opts?: InlineArtifactUpdateOptions) => void\n>();\n\n/**\n * Push the current session artifact registry state into every inline artifact\n * block under `root`. Blocks whose artifact has no registry record (e.g. after\n * a page refresh) are left on their props render — that render already carries\n * the complete content once the client embedded it on `artifact_complete`.\n *\n * v1 tradeoff: an empty registry (e.g. `clearArtifacts()` mid-stream) is a\n * no-op, so a block already showing pushed content stays frozen on it rather\n * than reverting to its props render. Resetting to props on every clear would\n * churn the common case and reload live file-preview iframes; accepted as-is.\n */\nexport function updateInlineArtifactBlocks(\n  root: HTMLElement,\n  artifacts: PersonaArtifactRecord[],\n  opts?: InlineArtifactUpdateOptions\n): void {\n  if (artifacts.length === 0) return;\n  const byId = new Map(artifacts.map((a) => [a.id, a]));\n  root.querySelectorAll<HTMLElement>(\"[data-artifact-inline]\").forEach((el) => {\n    const update = inlineBlockUpdaters.get(el);\n    if (!update) return;\n    const record = byId.get(el.getAttribute(\"data-artifact-inline\") ?? \"\");\n    if (record) update(record, opts);\n  });\n}\n\n/**\n * Whether `el` is an inline artifact block root with a live registry updater.\n * Lets the message renderer (ui.ts) reuse a mounted block across bubble\n * rebuilds instead of replacing it.\n */\nexport function hasLiveInlineArtifactBlock(el: HTMLElement): boolean {\n  return inlineBlockUpdaters.has(el);\n}\n\n/** Rebuild a preview record from the persisted block props (hydration path). */\nfunction recordFromProps(props: Record<string, unknown>): PersonaArtifactRecord {\n  const id = typeof props.artifactId === \"string\" ? props.artifactId : \"\";\n  const title =\n    typeof props.title === \"string\" && props.title ? props.title : undefined;\n  const status = props.status === \"streaming\" ? \"streaming\" : \"complete\";\n  if (props.artifactType === \"component\") {\n    const embedded = props.componentProps;\n    const componentProps =\n      embedded && typeof embedded === \"object\" && !Array.isArray(embedded)\n        ? (embedded as Record<string, unknown>)\n        : {};\n    return {\n      id,\n      artifactType: \"component\",\n      title,\n      status,\n      component: typeof props.component === \"string\" ? props.component : \"\",\n      props: componentProps\n    };\n  }\n  const file =\n    props.file && typeof props.file === \"object\" && !Array.isArray(props.file)\n      ? (props.file as PersonaArtifactFileMeta)\n      : undefined;\n  return {\n    id,\n    artifactType: \"markdown\",\n    title,\n    status,\n    markdown: typeof props.markdown === \"string\" ? props.markdown : \"\",\n    ...(file ? { file } : {})\n  };\n}\n\n/** Title (basename for file artifacts) + type label, derived like the card. */\nfunction chromeLabelsFor(record: PersonaArtifactRecord): {\n  title: string;\n  typeLabel: string;\n} {\n  const file = record.artifactType === \"markdown\" ? record.file : undefined;\n  const title = file\n    ? basenameOf(file.path)\n    : record.title && record.title.trim()\n      ? record.title\n      : \"Untitled artifact\";\n  const typeLabel = file\n    ? fileTypeLabel(file)\n    : record.artifactType === \"component\"\n      ? \"Component\"\n      : \"Document\";\n  return { title, typeLabel };\n}\n\n/**\n * Build the card component's prop shape from a live preview record, for the\n * `completeDisplay: \"card\"` collapse. The card renderer reads title / status /\n * type / id and (for markdown records) markdown + file meta; component records\n * only need id + title + type. Mirrors the prop keys `recordFromProps` reads.\n */\nfunction cardPropsFromRecord(\n  record: PersonaArtifactRecord\n): Record<string, unknown> {\n  const props: Record<string, unknown> = {\n    artifactId: record.id,\n    title: record.title ?? \"\",\n    status: record.status,\n    artifactType: record.artifactType\n  };\n  if (record.artifactType === \"markdown\") {\n    if (typeof record.markdown === \"string\") props.markdown = record.markdown;\n    if (record.file) props.file = record.file;\n  }\n  return props;\n}\n\n/**\n * Tuning knobs for the `completeDisplay: \"card\"` collapse handoff (see\n * animateInlineCollapse below). All values are milliseconds unless noted.\n */\n/** Phase 1: how long the streamed chrome + body fade out in place. */\nconst COLLAPSE_FADE_OUT_MS = 180;\n/** Phase 2 height collapse: base duration before the distance term. */\nconst COLLAPSE_HEIGHT_BASE_MS = 240;\n/** Phase 2 height collapse: extra ms per pixel of collapse distance. */\nconst COLLAPSE_HEIGHT_MS_PER_PX = 0.8;\n/** Phase 2 height collapse: duration clamp (min/max). */\nconst COLLAPSE_HEIGHT_MIN_MS = 300;\nconst COLLAPSE_HEIGHT_MAX_MS = 500;\n/** Phase 2 height collapse: easing (M3 standard curve). */\nconst COLLAPSE_HEIGHT_EASING = \"cubic-bezier(0.2, 0, 0, 1)\";\n/** Card fade-in duration. */\nconst COLLAPSE_CARD_FADE_IN_MS = 240;\n/**\n * Card fade-in start, as a fraction of the height duration (0 = fade in with\n * the collapse, 1 = only after the height has fully settled).\n */\nconst COLLAPSE_CARD_FADE_DELAY_FRACTION = 0.35;\n\n/**\n * Collapse animation for `completeDisplay: \"card\"`: fade the streamed chrome +\n * body out under a locked height, then `swap()` in the card and collapse the\n * height down to it while the card fades in. `onSettled` fires exactly once at\n * the end so the caller can hand updater ownership back to the card. Timings\n * live in the COLLAPSE_* constants above; the height duration scales with the\n * collapse distance.\n *\n * Runs on the Web Animations API, not CSS transitions: the completion window\n * re-renders the transcript several times (props embed, turn/execution\n * completion, lazy-chunk rebuilds) and may reparent this root, and a DOM move\n * cancels CSS transitions but not `element.animate()`.\n *\n * Degrades to an instant swap under `prefers-reduced-motion`, without layout\n * (detached / zero-height measure in tests), or without `element.animate`.\n * `swap()` and `onSettled()` always run.\n */\nfunction animateInlineCollapse(\n  root: HTMLElement,\n  startHeight: number,\n  opts: { swap: () => void; onSettled: () => void }\n): void {\n  const { swap, onSettled } = opts;\n  let reduceMotion = false;\n  try {\n    reduceMotion =\n      typeof window !== \"undefined\" &&\n      typeof window.matchMedia === \"function\" &&\n      window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n  } catch {\n    reduceMotion = false;\n  }\n  if (\n    !startHeight ||\n    !root.isConnected ||\n    reduceMotion ||\n    typeof root.animate !== \"function\"\n  ) {\n    swap();\n    onSettled();\n    return;\n  }\n\n  // Phase 1 — fade the streamed content out under a locked height. The lock\n  // is inline style (survives DOM moves); the fades are WAAPI (survive too).\n  root.style.height = `${startHeight}px`;\n  root.style.overflow = \"hidden\";\n  const fadeOuts: Animation[] = [];\n  for (const child of Array.from(root.children)) {\n    if (child instanceof HTMLElement) {\n      fadeOuts.push(\n        child.animate([{ opacity: 1 }, { opacity: 0 }], {\n          duration: COLLAPSE_FADE_OUT_MS,\n          easing: \"ease-out\",\n          fill: \"forwards\"\n        })\n      );\n    }\n  }\n\n  window.setTimeout(() => {\n    // The faded-out children are about to be replaced; drop their fills so\n    // the animations don't linger.\n    for (const anim of fadeOuts) anim.cancel();\n    swap();\n    const clearRoot = () => {\n      root.style.removeProperty(\"height\");\n      root.style.removeProperty(\"overflow\");\n    };\n    if (!root.isConnected) {\n      clearRoot();\n      onSettled();\n      return;\n    }\n\n    // Measure the card height without painting a frame: auto → read → relock.\n    root.style.height = \"auto\";\n    const endHeight = root.getBoundingClientRect().height;\n    root.style.height = `${startHeight}px`;\n    if (!endHeight || Math.abs(endHeight - startHeight) < 1) {\n      clearRoot();\n      onSettled();\n      return;\n    }\n\n    // Phase 2 — collapse the height while the card fades in.\n    const distance = Math.abs(startHeight - endHeight);\n    const duration = Math.round(\n      Math.min(\n        COLLAPSE_HEIGHT_MAX_MS,\n        Math.max(\n          COLLAPSE_HEIGHT_MIN_MS,\n          COLLAPSE_HEIGHT_BASE_MS + distance * COLLAPSE_HEIGHT_MS_PER_PX\n        )\n      )\n    );\n    const cardFadeDelay = Math.round(\n      duration * COLLAPSE_CARD_FADE_DELAY_FRACTION\n    );\n    const card =\n      root.firstElementChild instanceof HTMLElement\n        ? root.firstElementChild\n        : null;\n    const cardFade = card\n      ? card.animate([{ opacity: 0 }, { opacity: 1 }], {\n          duration: COLLAPSE_CARD_FADE_IN_MS,\n          delay: cardFadeDelay,\n          easing: \"ease-out\",\n          fill: \"backwards\"\n        })\n      : null;\n    const heightAnim = root.animate(\n      [{ height: `${startHeight}px` }, { height: `${endHeight}px` }],\n      { duration, easing: COLLAPSE_HEIGHT_EASING }\n    );\n    // The animation overrides the inline lock while it plays; park the inline\n    // height on the end value so there is no jump when it finishes.\n    root.style.height = `${endHeight}px`;\n\n    let done = false;\n    const clear = () => {\n      if (done) return;\n      done = true;\n      clearRoot();\n      onSettled();\n    };\n    // Settle when BOTH animations are done (the card fade can outlast the\n    // height collapse); a cancelled animation rejects `finished`, which\n    // allSettled treats as done.\n    Promise.allSettled(\n      [heightAnim.finished, cardFade?.finished].filter(Boolean)\n    ).then(clear);\n    // Fallback in case `finished` never resolves (interrupted layout, etc.).\n    // Covers whichever ends later: the height collapse or the card fade-in.\n    window.setTimeout(\n      clear,\n      Math.max(duration, cardFadeDelay + COLLAPSE_CARD_FADE_IN_MS) + 120\n    );\n  }, COLLAPSE_FADE_OUT_MS);\n}\n\n/** Resolve the inline chrome config knobs (chrome on by default). */\nfunction resolveInlineChrome(cfg: AgentWidgetArtifactsFeature | undefined): {\n  chromeEnabled: boolean;\n  showCopy: boolean;\n  showExpand: boolean;\n  showViewToggle: boolean;\n} {\n  const inlineChrome = cfg?.inlineChrome;\n  if (inlineChrome === false) {\n    return {\n      chromeEnabled: false,\n      showCopy: false,\n      showExpand: false,\n      showViewToggle: false\n    };\n  }\n  const opts = typeof inlineChrome === \"object\" ? inlineChrome : undefined;\n  return {\n    chromeEnabled: true,\n    showCopy: opts?.showCopy !== false,\n    showExpand: opts?.showExpand !== false,\n    showViewToggle: opts?.showViewToggle !== false\n  };\n}\n\n/** CSS var carrying the current-state numeric body height (px); unset = \"auto\". */\nconst BODY_HEIGHT_VAR = \"--persona-artifact-inline-body-height\";\n\n/**\n * Resolve `features.artifacts.inlineBody` into the flat {@link ArtifactBodyLayout}\n * the shared preview renderer consumes. Defaults: 320px both states, source\n * streaming view, rendered complete view, tail-follow on, top+bottom edge fade\n * (each edge clip-gated at render time), auto transition.\n */\nexport function resolveInlineBody(\n  cfg: AgentWidgetArtifactsFeature | undefined\n): ArtifactBodyLayout {\n  const b = cfg?.inlineBody;\n  const streamingView = b?.streamingView === \"status\" ? \"status\" : \"source\";\n  const viewMode = b?.viewMode === \"source\" ? \"source\" : \"rendered\";\n\n  let streamingHeight: number | \"auto\" = 320;\n  let completeHeight: number | \"auto\" = 320;\n  const h = b?.height;\n  if (typeof h === \"number\" || h === \"auto\") {\n    streamingHeight = h;\n    completeHeight = h;\n  } else if (h && typeof h === \"object\") {\n    streamingHeight = h.streaming ?? 320;\n    completeHeight = h.complete ?? 320;\n  }\n\n  // Clip mode shows the top of the document in a static window: no tail-follow\n  // (followOutput is ignored — the window never scrolls), and its unset-fadeMask\n  // default flips to bottom-only (the top is always visible, so a top fade would\n  // never be correct). An explicit fadeMask still wins below.\n  const overflow = b?.overflow === \"clip\" ? \"clip\" : \"scroll\";\n  const followOutput = overflow === \"clip\" ? false : b?.followOutput !== false;\n\n  // fadeMask: when unset, the default depends on the overflow mode — scroll gets\n  // both edges (top + bottom), clip gets bottom-only. An explicit value (boolean\n  // or object) always wins over that mode default. Each edge is clip-gated at\n  // runtime in artifact-preview.ts `updateFadeClasses` (only shown when that edge\n  // is actually clipped), so with scroll+tail-follow pinned at the bottom the\n  // bottom fade stays visually inert until the reader scrolls up mid-stream.\n  let fadeTop: boolean;\n  let fadeBottom: boolean;\n  const fm = b?.fadeMask;\n  if (fm === true) {\n    fadeTop = true;\n    fadeBottom = true;\n  } else if (fm === false) {\n    fadeTop = false;\n    fadeBottom = false;\n  } else if (fm && typeof fm === \"object\") {\n    fadeTop = fm.top === true;\n    fadeBottom = fm.bottom === true;\n  } else if (overflow === \"clip\") {\n    fadeTop = false;\n    fadeBottom = true;\n  } else {\n    fadeTop = true;\n    fadeBottom = true;\n  }\n\n  const transition = b?.transition === \"none\" ? \"none\" : \"auto\";\n  const completeDisplay = b?.completeDisplay === \"card\" ? \"card\" : \"inline\";\n\n  return {\n    streamingView,\n    viewMode,\n    streamingHeight,\n    completeHeight,\n    followOutput,\n    overflow,\n    fadeTop,\n    fadeBottom,\n    transition,\n    completeDisplay\n  };\n}\n\nfunction renderDefaultArtifactInline(\n  props: Record<string, unknown>,\n  context: ComponentContext\n): HTMLElement {\n  const record = recordFromProps(props);\n  const artifactsCfg = context.config?.features?.artifacts;\n  const { chromeEnabled, showCopy, showExpand, showViewToggle } =\n    resolveInlineChrome(artifactsCfg);\n  const inlineActions = artifactsCfg?.inlineActions ?? [];\n  const bodyLayout = resolveInlineBody(artifactsCfg);\n  const collapseToCard = bodyLayout.completeDisplay === \"card\";\n\n  // Per-block rendered/source view toggle state. `null` = follow the configured\n  // default (`bodyLayout.viewMode`); a set value is the user's explicit choice\n  // for this block. Reset to `null` when content restarts streaming or a\n  // different artifact id arrives (see the block updater below) so hydrated/new\n  // content starts on the configured default. `currentRecord` tracks the latest\n  // record so the toggle's direct click handler re-renders against fresh content.\n  let userViewMode: \"rendered\" | \"source\" | null = null;\n  let currentRecord = record;\n\n  const root = createElement(\n    \"div\",\n    \"persona-artifact-inline persona-w-full persona-max-w-full\"\n  );\n  root.setAttribute(\"data-persona-theme-zone\", \"artifact-inline\");\n  if (record.id) {\n    root.setAttribute(\"data-artifact-inline\", record.id);\n  }\n\n  // completeDisplay: \"card\" — collapse the block to the compact reference card\n  // once the artifact completes. `PersonaArtifactCard` is the public card entry,\n  // so a host `features.artifacts.renderCard` override still applies. The card\n  // is mounted on the SAME root (data-artifact-inline keying + theme zone\n  // survive); the frame chrome styling is dropped via a modifier class because\n  // the card carries its own border/background. The registered updater becomes a\n  // card-sync updater so later records (title/status changes) re-render the card.\n  const renderCardChild = (rec: PersonaArtifactRecord): HTMLElement =>\n    PersonaArtifactCard(cardPropsFromRecord(rec), context);\n  const mountCard = (rec: PersonaArtifactRecord): void => {\n    root.classList.add(\"persona-artifact-inline--card\");\n    root.style.removeProperty(BODY_HEIGHT_VAR);\n    root.replaceChildren(renderCardChild(rec));\n    inlineBlockUpdaters.set(root, (r) => {\n      root.replaceChildren(renderCardChild(r));\n    });\n  };\n\n  // Hydration: a block whose props arrive already-complete renders the card\n  // directly — no flash of the inline body, no collapse animation.\n  if (collapseToCard && record.status === \"complete\") {\n    mountCard(record);\n    return root;\n  }\n\n  const handle = renderArtifactPreviewBody(record, {\n    config: context.config,\n    bodyLayout,\n    // The user's per-block toggle wins over the configured default; `null`\n    // falls back to `bodyLayout.viewMode`, so with no toggle interaction this\n    // is behavior-identical to passing no `resolveViewMode` at all.\n    resolveViewMode: () => userViewMode ?? bodyLayout.viewMode\n  });\n\n  // Body wrapper is always present so the frame's padding lives here (chrome\n  // sits flush to the frame edge); required even when chrome is disabled.\n  const body = createElement(\"div\", \"persona-artifact-inline-body\");\n  body.appendChild(handle.el);\n\n  // Height model (inlineBody): set the current-state numeric height as a CSS\n  // var on the root, then classify the rendered body (post-render — the shared\n  // renderer owns the pre/iframe/markdown branching, mirroring the pane's flush\n  // toggle):\n  // - code body → `persona-artifact-content-flush`, full-bleed like the pane's\n  //   source view (gutter flush to the frame edge)\n  // - fill-capable body (fixed source window / iframe / status placeholder) →\n  //   the wrapper owns the reserved height (border-box), so flush and padded\n  //   states occupy the same outer box and every swap is layout neutral\n  // - flow body (rendered markdown, component) → complete-state max-height cap\n  const applyBodyHeight = (rec: PersonaArtifactRecord) => {\n    const streaming = rec.status !== \"complete\";\n    const h = streaming ? bodyLayout.streamingHeight : bodyLayout.completeHeight;\n    const numeric = typeof h === \"number\";\n    if (numeric) {\n      root.style.setProperty(BODY_HEIGHT_VAR, `${h}px`);\n    } else {\n      root.style.removeProperty(BODY_HEIGHT_VAR);\n    }\n    const hasCodePre = Boolean(handle.el.querySelector(\".persona-code-pre\"));\n    const fillCapable = Boolean(\n      handle.el.querySelector(\n        \"iframe, .persona-artifact-source-window--fixed, .persona-artifact-status-view\"\n      )\n    );\n    body.classList.toggle(\"persona-artifact-content-flush\", hasCodePre);\n    body.classList.toggle(\n      \"persona-artifact-inline-body--sized\",\n      numeric && fillCapable\n    );\n    body.classList.toggle(\n      \"persona-artifact-inline-body--cap\",\n      !streaming && !fillCapable && typeof bodyLayout.completeHeight === \"number\"\n    );\n  };\n\n  // Body updater: set the height var, run the (optionally animated) swap on the\n  // streaming→complete boundary, then re-apply the complete-state cap.\n  let lastBodyStatus: string = record.status;\n  const runBodyUpdate = (\n    rec: PersonaArtifactRecord,\n    opts?: InlineArtifactUpdateOptions\n  ) => {\n    const boundary = lastBodyStatus !== \"complete\" && rec.status === \"complete\";\n    if (boundary) {\n      // Pre-set the target (complete) height so the reserved streaming window\n      // and the completed body share a height and the swap is layout-neutral.\n      if (typeof bodyLayout.completeHeight === \"number\") {\n        root.style.setProperty(BODY_HEIGHT_VAR, `${bodyLayout.completeHeight}px`);\n      } else {\n        root.style.removeProperty(BODY_HEIGHT_VAR);\n      }\n      // Re-apply inside the swap too: with a live View Transition the swap\n      // callback runs after the snapshot, so the outer applyBodyHeight below\n      // would read the pre-swap DOM for iframe/window detection.\n      runArtifactBodyTransition(\n        body,\n        opts?.suppressTransition ? \"none\" : bodyLayout.transition,\n        rec.id,\n        () => {\n          handle.update(rec);\n          applyBodyHeight(rec);\n        }\n      );\n    } else {\n      handle.update(rec);\n    }\n    applyBodyHeight(rec);\n    lastBodyStatus = rec.status;\n  };\n\n  applyBodyHeight(record);\n\n  // Clip-mode expand hitbox: in `inlineBody.overflow: \"clip\"` the body shows a\n  // static top-of-document window with no internal scroll, so — when the chrome's\n  // Expand control is enabled and the record has an id — the whole body doubles\n  // as the expand affordance (industry inline-card pattern). Reuse the existing\n  // `data-expand-artifact-inline` delegation in ui.ts (matched via closest(), so\n  // a body-level attribute works; Enter/Space are handled by the same keydown\n  // delegation). The aria-label is refreshed in updateChrome as the title\n  // streams in. When Expand is disabled the clip stays purely visual.\n  const clipExpandHitbox =\n    bodyLayout.overflow === \"clip\" && showExpand && !!record.id;\n  if (clipExpandHitbox) {\n    body.setAttribute(\"data-expand-artifact-inline\", record.id);\n    body.setAttribute(\"role\", \"button\");\n    body.setAttribute(\"tabindex\", \"0\");\n    body.classList.add(\"persona-cursor-pointer\");\n    body.setAttribute(\n      \"aria-label\",\n      `Open ${chromeLabelsFor(record).title} in panel`\n    );\n  }\n\n  if (!chromeEnabled) {\n    root.appendChild(body);\n    inlineBlockUpdaters.set(root, runBodyUpdate);\n    return root;\n  }\n\n  // Chrome bar: [icon][title][type/status] … [inlineActions][copy][expand].\n  const chrome = createElement(\"div\", \"persona-artifact-inline-chrome\");\n  chrome.setAttribute(\"data-persona-theme-zone\", \"artifact-inline-chrome\");\n\n  const lead = createElement(\"div\", \"persona-artifact-inline-chrome-lead\");\n  const iconWrap = createElement(\n    \"span\",\n    \"persona-flex persona-items-center persona-flex-shrink-0\"\n  );\n  const icon = renderLucideIcon(\"file-text\", 16, \"currentColor\", 2);\n  if (icon) iconWrap.appendChild(icon);\n  const titleEl = createElement(\n    \"span\",\n    \"persona-artifact-inline-title persona-truncate persona-min-w-0\"\n  );\n  // Meta span toggles between the type label (complete) and the animated\n  // streaming status; rebuilt in place on every update.\n  const metaEl = createElement(\"span\", \"persona-artifact-inline-type\");\n  lead.append(iconWrap, titleEl, metaEl);\n\n  const actions = createElement(\"div\", \"persona-artifact-inline-chrome-actions\");\n  // Custom inline actions are rebuilt per update so per-artifact visible() gates\n  // re-evaluate as content materializes; they carry no direct listeners (clicks\n  // are delegated in ui.ts via data-artifact-custom-action, keyed to the nearest\n  // [data-artifact-inline] container).\n  const customActionsWrap = createElement(\n    \"span\",\n    \"persona-flex persona-items-center persona-gap-1\"\n  );\n\n  const effectiveViewMode = (): \"rendered\" | \"source\" =>\n    userViewMode ?? bodyLayout.viewMode;\n\n  // Rendered/source view toggle: the same segmented toggle group the artifact\n  // pane uses (createToggleGroup), so the inline chrome matches the pane's UX.\n  // Both segments (eye \"Rendered view\", code-xml \"Source\") are always visible and\n  // the active one is highlighted. Created only when enabled; per-record\n  // availability is gated in updateChrome via persona-hidden on the whole group,\n  // like copy. updateChrome also syncs the selected segment on every update.\n  const viewToggle = showViewToggle\n    ? createToggleGroup({\n        items: [\n          {\n            id: \"rendered\",\n            icon: \"eye\",\n            label: \"Rendered view\",\n            className: \"persona-artifact-doc-icon-btn persona-artifact-view-btn\"\n          },\n          {\n            id: \"source\",\n            icon: \"code-xml\",\n            label: \"Source\",\n            className: \"persona-artifact-doc-icon-btn persona-artifact-code-btn\"\n          }\n        ],\n        selectedId: effectiveViewMode(),\n        className: \"persona-artifact-toggle-group persona-flex-shrink-0\",\n        // Direct handlers via the group's own click listeners (deliberate\n        // deviation from the delegated copy/expand pattern): the toggle is purely\n        // block-local state + a re-render of this block's own preview handle. It\n        // needs no session access, and the chrome element is stable for the\n        // block's lifetime (never remounted), so direct handlers are simpler and\n        // correct here — no delegation round-trip needed.\n        onSelect: (id) => {\n          const next = id === \"source\" ? \"source\" : \"rendered\";\n          // Selecting the already-active segment is a no-op (createToggleGroup\n          // still fires onSelect on a re-click of the active segment).\n          if (next === effectiveViewMode()) return;\n          userViewMode = next;\n          // Known tradeoff: toggling away from a live file-preview iframe drops it,\n          // and toggling back rebuilds it (srcdoc reload → iframe-internal state\n          // lost). Same behavior as the pane's toggle; we deliberately do not keep\n          // a detached iframe alive.\n          //\n          // Swap instantly — no runArtifactBodyTransition here. The View\n          // Transition exists for the streaming→complete swap; on a toggle\n          // click its document-wide snapshot crossfade would paint over the\n          // group's sliding-thumb animation (which plays inside the snapshot\n          // window and reads as a plain dissolve). The pane's toggle also\n          // swaps its body instantly, so this keeps the two in parity: the\n          // thumb slide IS the toggle's animation.\n          handle.update(currentRecord);\n          applyBodyHeight(currentRecord);\n        }\n      })\n    : null;\n\n  const copyBtn = showCopy\n    ? createIconButton({\n        icon: \"copy\",\n        label: \"Copy\",\n        className: \"persona-artifact-doc-icon-btn persona-flex-shrink-0\"\n      })\n    : null;\n  if (copyBtn && record.id) {\n    // No direct listener: delegated in ui.ts via data-copy-artifact so the\n    // handler survives re-renders and page-refresh hydration.\n    copyBtn.setAttribute(\"data-copy-artifact\", record.id);\n  }\n\n  const expandBtn = showExpand\n    ? createIconButton({\n        icon: \"maximize\",\n        label: \"Open in panel\",\n        className: \"persona-artifact-doc-icon-btn persona-flex-shrink-0\"\n      })\n    : null;\n  if (expandBtn && record.id) {\n    // Delegated in ui.ts via data-expand-artifact-inline (opens the pane).\n    expandBtn.setAttribute(\"data-expand-artifact-inline\", record.id);\n  }\n\n  actions.appendChild(customActionsWrap);\n  if (viewToggle) actions.appendChild(viewToggle.element);\n  if (copyBtn) actions.appendChild(copyBtn);\n  if (expandBtn) actions.appendChild(expandBtn);\n\n  chrome.append(lead, actions);\n  root.append(chrome, body);\n\n  const updateChrome = (rec: PersonaArtifactRecord) => {\n    const { title, typeLabel } = chromeLabelsFor(rec);\n    titleEl.textContent = title;\n    titleEl.title = title;\n    // Keep the clip-mode body hitbox's label in sync with the streaming title.\n    if (clipExpandHitbox) {\n      body.setAttribute(\"aria-label\", `Open ${title} in panel`);\n    }\n\n    const streaming = rec.status !== \"complete\";\n    if (streaming) {\n      // Animation-stable: only switch the meta span into status mode (clearing\n      // its children) on the type→status transition. On subsequent deltas the\n      // span is left in place so applyArtifactStatus can reuse the animated\n      // label span (re-applied only when the label text changes) and update the\n      // plain detail span freely.\n      if (!metaEl.classList.contains(\"persona-artifact-inline-status\")) {\n        metaEl.className = \"persona-artifact-inline-status\";\n        metaEl.removeAttribute(\"data-preserve-animation\");\n        metaEl.replaceChildren();\n      }\n      const resolved = resolveArtifactStatusLabel(rec, artifactsCfg, \"inline-chrome\");\n      applyArtifactStatus(metaEl, resolved, artifactsCfg);\n    } else {\n      // Reset the meta span (className replace clears stale animation classes).\n      clearArtifactStatusTracking(rec.id);\n      metaEl.className = \"persona-artifact-inline-type\";\n      metaEl.removeAttribute(\"data-preserve-animation\");\n      metaEl.replaceChildren();\n      metaEl.textContent = typeLabel;\n    }\n\n    // Copy + custom actions only act on complete content; expand stays visible\n    // through streaming so it is always available.\n    if (copyBtn) copyBtn.classList.toggle(\"persona-hidden\", streaming);\n\n    // View toggle availability: complete file artifacts that actually have a\n    // rendered alternative to their source. Plain markdown (no file meta) and\n    // component artifacts are hidden here (v1 decision: the pane toggle covers\n    // them). Also hidden when the host forced source-only via\n    // `inlineBody.viewMode: \"source\"` — there is no preview to switch to.\n    if (viewToggle) {\n      const file = rec.artifactType === \"markdown\" ? rec.file : undefined;\n      let canToggle = false;\n      if (!streaming && file && bodyLayout.viewMode !== \"source\") {\n        const kind = fileKindOf(file);\n        if (kind === \"markdown\") {\n          canToggle = true;\n        } else if (kind === \"html\" || kind === \"svg\") {\n          canToggle = artifactsCfg?.filePreview?.enabled !== false;\n        }\n        // kind \"other\" → source-only, no rendered alternative → stays hidden.\n      }\n      viewToggle.element.classList.toggle(\"persona-hidden\", !canToggle);\n      // Sync the group's selected segment on every update: inlineBlockUpdaters\n      // resets userViewMode to null on re-stream / new id, so the group must snap\n      // back to the configured default here.\n      viewToggle.setSelected(effectiveViewMode());\n    }\n\n    customActionsWrap.replaceChildren();\n    if (!streaming && inlineActions.length > 0) {\n      const ctx = artifactRecordActionContext(rec);\n      if (ctx) {\n        for (const action of inlineActions) {\n          try {\n            if (action.visible === undefined || action.visible(ctx)) {\n              const btn = buildArtifactActionButton(action, { documentChrome: true });\n              btn.setAttribute(\"data-artifact-custom-action\", action.id);\n              btn.classList.add(\"persona-flex-shrink-0\");\n              customActionsWrap.appendChild(btn);\n            }\n          } catch {\n            // A single bad action must not take down the whole chrome.\n          }\n        }\n      }\n    }\n  };\n\n  updateChrome(record);\n\n  inlineBlockUpdaters.set(root, (rec, opts) => {\n    // completeDisplay: \"card\" — on the streaming→complete boundary, collapse the\n    // whole block (chrome + body) to the reference card on the same root instead\n    // of swapping the body. Measure the reserved streaming height first, then\n    // run the staged collapse (fade out → card mount → height collapse). The\n    // WAAPI collapse replaces the body's View Transition here, so\n    // `runBodyUpdate` is deliberately bypassed — it must not double-fire\n    // runArtifactBodyTransition for this boundary.\n    if (\n      collapseToCard &&\n      lastBodyStatus !== \"complete\" &&\n      rec.status === \"complete\"\n    ) {\n      const startHeight = root.isConnected\n        ? root.getBoundingClientRect().height\n        : 0;\n      lastBodyStatus = \"complete\";\n      // Buffer records for the whole animation — a card-sync replaceChildren\n      // mid-collapse would cut the card fade. onSettled hands ownership back\n      // to the card-sync updater.\n      let latest = rec;\n      let dirtySinceSwap = false;\n      const buffer = (r: PersonaArtifactRecord) => {\n        latest = r;\n        dirtySinceSwap = true;\n      };\n      inlineBlockUpdaters.set(root, buffer);\n      animateInlineCollapse(root, startHeight, {\n        swap: () => {\n          mountCard(latest);\n          dirtySinceSwap = false;\n          inlineBlockUpdaters.set(root, buffer);\n        },\n        onSettled: () => {\n          // mountCard re-registers the card-sync updater; skip the re-render\n          // when nothing changed while the animation ran.\n          if (dirtySinceSwap) {\n            mountCard(latest);\n          } else {\n            inlineBlockUpdaters.set(root, (r) => {\n              root.replaceChildren(renderCardChild(r));\n            });\n          }\n        }\n      });\n      return;\n    }\n    // Reset the per-block view toggle when content restarts streaming or a\n    // different artifact id arrives, so hydrated/new content starts on the\n    // configured default. Must run before runBodyUpdate → handle.update reads\n    // userViewMode via resolveViewMode.\n    if (rec.status !== \"complete\" || rec.id !== currentRecord.id) {\n      userViewMode = null;\n    }\n    currentRecord = rec;\n    runBodyUpdate(rec, opts);\n    updateChrome(rec);\n  });\n\n  return root;\n}\n\nexport const PersonaArtifactInline: ComponentRenderer = (props, context) => {\n  const customRenderer = context?.config?.features?.artifacts?.renderInline;\n  if (customRenderer) {\n    const title =\n      typeof props.title === \"string\" && props.title\n        ? props.title\n        : \"Untitled artifact\";\n    const artifactId =\n      typeof props.artifactId === \"string\" ? props.artifactId : \"\";\n    const status = props.status === \"streaming\" ? \"streaming\" : \"complete\";\n    const artifactType =\n      typeof props.artifactType === \"string\" ? props.artifactType : \"markdown\";\n\n    const result = customRenderer({\n      artifact: { artifactId, title, artifactType, status },\n      config: context.config,\n      defaultRenderer: () => renderDefaultArtifactInline(props, context)\n    });\n    if (result) return result;\n  }\n\n  return renderDefaultArtifactInline(props, context);\n};\n","import { AgentWidgetConfig, AgentWidgetMessage } from \"../types\";\nimport { PersonaArtifactCard } from \"./artifact-card\";\nimport { PersonaArtifactInline } from \"./artifact-inline\";\n\n/**\n * Context provided to component renderers\n */\nexport interface ComponentContext {\n  message: AgentWidgetMessage;\n  config: AgentWidgetConfig;\n  /**\n   * Update component props during streaming\n   */\n  updateProps: (newProps: Record<string, unknown>) => void;\n}\n\n/**\n * Component renderer function signature\n */\nexport type ComponentRenderer = (\n  props: Record<string, unknown>,\n  context: ComponentContext\n) => HTMLElement;\n\n/**\n * Per-component registration options\n */\nexport interface ComponentRegistrationOptions {\n  /**\n   * When false, the component directive renders bare in the thread (no\n   * persona-message-bubble chrome).\n   * @default true\n   */\n  bubbleChrome?: boolean;\n}\n\n/**\n * Component registry for managing custom components\n */\nclass ComponentRegistry {\n  private components: Map<string, ComponentRenderer> = new Map();\n  private options: Map<string, ComponentRegistrationOptions> = new Map();\n\n  /**\n   * Register a custom component\n   */\n  register(\n    name: string,\n    renderer: ComponentRenderer,\n    options?: ComponentRegistrationOptions\n  ): void {\n    if (this.components.has(name)) {\n      console.warn(`[ComponentRegistry] Component \"${name}\" is already registered. Overwriting.`);\n    }\n    this.components.set(name, renderer);\n    if (options) {\n      this.options.set(name, options);\n    } else {\n      this.options.delete(name);\n    }\n  }\n\n  /**\n   * Unregister a component\n   */\n  unregister(name: string): void {\n    this.components.delete(name);\n    this.options.delete(name);\n  }\n\n  /**\n   * Get a component renderer by name\n   */\n  get(name: string): ComponentRenderer | undefined {\n    return this.components.get(name);\n  }\n\n  /**\n   * Check if a component is registered\n   */\n  has(name: string): boolean {\n    return this.components.has(name);\n  }\n\n  /**\n   * Get the registration options for a component, if any were supplied\n   */\n  getOptions(name: string): ComponentRegistrationOptions | undefined {\n    return this.options.get(name);\n  }\n\n  /**\n   * Get all registered component names\n   */\n  getAllNames(): string[] {\n    return Array.from(this.components.keys());\n  }\n\n  /**\n   * Clear all registered components\n   */\n  clear(): void {\n    this.components.clear();\n    this.options.clear();\n  }\n\n  /**\n   * Register multiple components at once\n   */\n  registerAll(components: Record<string, ComponentRenderer>): void {\n    Object.entries(components).forEach(([name, renderer]) => {\n      this.register(name, renderer);\n    });\n  }\n}\n\n/**\n * Global component registry instance\n */\nexport const componentRegistry = new ComponentRegistry();\n\n// Register built-in components. The artifact card and inline block carry\n// their own border and surface, so they render bare in the thread to avoid\n// double-boxing.\ncomponentRegistry.register(\"PersonaArtifactCard\", PersonaArtifactCard, { bubbleChrome: false });\ncomponentRegistry.register(\"PersonaArtifactInline\", PersonaArtifactInline, { bubbleChrome: false });\n","import { createElement } from \"../utils/dom\";\nimport { createSpinner } from \"../utils/spinner\";\nimport type {\n  AgentWidgetConfig,\n  AgentWidgetMessage,\n  PersonaArtifactRecord,\n} from \"../types\";\nimport { extractFileSource, fileKindOf } from \"../utils/artifact-file\";\nimport {\n  applyArtifactStatus,\n  clearArtifactStatusTracking,\n  resolveArtifactStatusLabel\n} from \"../utils/artifact-status-label\";\nimport { highlightCode } from \"../utils/code-highlight\";\nimport { escapeHtml, createMarkdownProcessorFromConfig } from \"../postprocessors\";\nimport { getMarkdownParsersSync, onMarkdownParsersReady } from \"../markdown-parsers-loader\";\nimport { resolveSanitizer } from \"../utils/sanitize\";\nimport {\n  componentRegistry,\n  type ComponentContext,\n  type ComponentRenderer,\n} from \"./registry\";\n\n/**\n * Shared artifact preview body renderer.\n *\n * Renders the *body* of an artifact — rendered markdown, sandboxed file\n * preview iframe, or registry-resolved component — without any surrounding\n * chrome. The artifact pane delegates its content area here, and the inline\n * transcript block (display: \"inline\") consumes the same renderer, so both\n * surfaces stay pixel- and behavior-identical.\n */\n\nexport type ArtifactPreviewViewMode = \"rendered\" | \"source\";\n\n/**\n * Clipboard payload for an artifact, shared by the pane copy control and the\n * inline chrome copy button so both derive the same text:\n * - plain markdown artifacts → the raw markdown\n * - previewable file artifacts → the raw unfenced file source\n * - component artifacts → pretty-printed `{ component, props }` JSON\n */\nexport function artifactCopyText(\n  record: PersonaArtifactRecord | undefined\n): string {\n  if (!record) return \"\";\n  if (record.artifactType === \"markdown\") {\n    const raw = record.markdown ?? \"\";\n    return record.file ? extractFileSource(raw) : raw;\n  }\n  return JSON.stringify(\n    { component: record.component, props: record.props },\n    null,\n    2\n  );\n}\n\n/**\n * Resolved body-layout options for the inline block. Threaded through\n * {@link ArtifactPreviewContext.bodyLayout} by `artifact-inline.ts` (resolved\n * from `features.artifacts.inlineBody`). When absent — the pane path — the body\n * renders content-sized exactly as before; none of the fixed-window / follow /\n * fade / status / transition behavior below engages.\n */\nexport type ArtifactBodyLayout = {\n  /** Streaming body: `\"source\"` shows the source window, `\"status\"` a placeholder. */\n  streamingView: \"source\" | \"status\";\n  /**\n   * Complete-state view: `\"source\"` always shows raw highlighted source (no\n   * iframe, no rendered markdown). `ctx.resolveViewMode` still wins when set.\n   */\n  viewMode: \"rendered\" | \"source\";\n  /** Reserved streaming height (px) or `\"auto\"` (grow with content). */\n  streamingHeight: number | \"auto\";\n  /** Complete-state height cap (px) or `\"auto\"` (content-sized). */\n  completeHeight: number | \"auto\";\n  /** Tail-follow the newest lines while a numeric-height source window streams. */\n  followOutput: boolean;\n  /**\n   * Overflow handling for a fixed-height source window. `\"scroll\"` (default) is\n   * an internally scrollable, tail-following window; `\"clip\"` is a fixed-height\n   * window showing the TOP of the document with `overflow: hidden`, no internal\n   * scroll listeners, and no tail-follow (`resolveInlineBody` forces\n   * `followOutput` off and the bottom-only fade default in this mode).\n   */\n  overflow: \"scroll\" | \"clip\";\n  /** Show a top edge fade on the fixed window when content is clipped above. */\n  fadeTop: boolean;\n  /** Show a bottom edge fade on the fixed window when content is clipped below. */\n  fadeBottom: boolean;\n  /** Wrap the streaming→complete swap in a View Transition when supported. */\n  transition: \"auto\" | \"none\";\n  /**\n   * What the inline block becomes once the artifact completes. `\"inline\"`\n   * (default) keeps the streamed body in place; `\"card\"` collapses the block to\n   * the compact reference card. Consumed only by the inline block component\n   * (`artifact-inline.ts`) — the shared preview renderer ignores it.\n   */\n  completeDisplay: \"inline\" | \"card\";\n};\n\nexport type ArtifactPreviewContext = {\n  config: AgentWidgetConfig;\n  /**\n   * Component registry used to resolve component artifacts. Defaults to the\n   * global `componentRegistry`.\n   */\n  registry?: { get(name: string): ComponentRenderer | undefined };\n  /**\n   * Host hook for the pane's rendered/source toolbar toggle. Re-read on every\n   * `update()`; return `\"source\"` to show raw source instead of the preview\n   * for the given record. Only affects markdown and file records — component\n   * records always render through the registry. Defaults to `\"rendered\"`.\n   */\n  resolveViewMode?: (record: PersonaArtifactRecord) => ArtifactPreviewViewMode;\n  /**\n   * Inline-block body layout (fixed streaming window, tail-follow, fade,\n   * status view). Set only on the inline path; when omitted the body is\n   * content-sized as before (pane path).\n   */\n  bodyLayout?: ArtifactBodyLayout;\n};\n\n/**\n * Module-level guard so only one inline body swap animates at a time — a burst\n * of artifacts completing together must not stack overlapping View Transitions\n * (each captures the whole document).\n */\nlet bodyTransitionInFlight = false;\n\n/**\n * `view-transition-name` must be a valid CSS custom-ident; artifact ids may\n * carry characters that aren't, so map anything outside `[A-Za-z0-9_-]` to `-`\n * and prefix a stable namespace (idents also can't start with a digit).\n */\nfunction sanitizeViewTransitionName(id: string): string {\n  const base = (id || \"artifact\").replace(/[^A-Za-z0-9_-]/g, \"-\").replace(/^-+/, \"\");\n  return \"persona-artifact-vt-\" + (base || \"artifact\");\n}\n\n/**\n * Run the streaming→complete body swap, animated via the View Transitions API\n * when `transition` is `\"auto\"`, the API exists, `prefers-reduced-motion` is\n * off, and no other body swap is mid-flight; otherwise swap instantly. The swap\n * callback performs the actual DOM mutation either way, so callers always see\n * the new DOM (synchronously on the fallback path).\n */\nexport function runArtifactBodyTransition(\n  target: HTMLElement | null,\n  transition: \"auto\" | \"none\",\n  id: string,\n  swap: () => void\n): void {\n  const start =\n    typeof document !== \"undefined\"\n      ? (\n          document as unknown as {\n            startViewTransition?: (cb: () => void) => { finished: Promise<void> };\n          }\n        ).startViewTransition\n      : undefined;\n  let reduceMotion = false;\n  try {\n    reduceMotion =\n      typeof window !== \"undefined\" &&\n      typeof window.matchMedia === \"function\" &&\n      window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n  } catch {\n    reduceMotion = false;\n  }\n  if (\n    transition !== \"auto\" ||\n    !target ||\n    typeof start !== \"function\" ||\n    bodyTransitionInFlight ||\n    reduceMotion\n  ) {\n    swap();\n    return;\n  }\n  bodyTransitionInFlight = true;\n  const name = sanitizeViewTransitionName(id);\n  target.style.setProperty(\"view-transition-name\", name);\n  const done = () => {\n    bodyTransitionInFlight = false;\n    target.style.removeProperty(\"view-transition-name\");\n  };\n  try {\n    const vt = start.call(document, () => {\n      swap();\n    });\n    vt.finished.then(done, done);\n  } catch {\n    done();\n    swap();\n  }\n}\n\nexport type ArtifactPreviewBodyHandle = {\n  /**\n   * Stable wrapper element (styled `display: contents`, so children lay out\n   * as if they were direct children of the host container). Keep it attached\n   * across `update()` calls: re-attaching would reload a live file-preview\n   * iframe.\n   */\n  el: HTMLElement;\n  /** Apply streaming deltas / status changes (may be a different record). */\n  update(record: PersonaArtifactRecord): void;\n};\n\nconst PRE_CLASS =\n  \"persona-font-mono persona-text-xs persona-whitespace-pre-wrap persona-break-words persona-text-persona-primary\";\nconst MARKDOWN_WRAP_CLASS =\n  \"persona-text-sm persona-leading-relaxed persona-markdown-bubble\";\n\n/** Inspector card shown when a component artifact has no registered renderer. */\nfunction fallbackComponentCard(record: PersonaArtifactRecord): HTMLElement {\n  const card = createElement(\n    \"div\",\n    \"persona-rounded-lg persona-border persona-border-persona-border persona-p-3 persona-text-persona-primary\"\n  );\n  const title = createElement(\"div\", \"persona-font-semibold persona-text-sm persona-mb-2\");\n  title.textContent = record.component ? `Component: ${record.component}` : \"Component\";\n  const pre = createElement(\n    \"pre\",\n    \"persona-font-mono persona-text-xs persona-whitespace-pre-wrap persona-overflow-x-auto\"\n  );\n  pre.textContent = JSON.stringify(record.props ?? {}, null, 2);\n  card.appendChild(title);\n  card.appendChild(pre);\n  return card;\n}\n\n/** Render context handed to a custom `filePreview.loading.renderIndicator`. */\ntype PreviewIndicatorContext = {\n  artifactId: string;\n  config: AgentWidgetConfig;\n};\n\ntype PreviewLoadingObject = {\n  delayMs?: number;\n  minVisibleMs?: number;\n  timeoutMs?: number;\n  injectReadySignal?: boolean;\n  label?: string | false;\n  labelDelayMs?: number;\n  renderIndicator?: (ctx: PreviewIndicatorContext) => HTMLElement | null;\n};\n\n/** Resolved preview-loading options (see `filePreview.loading`). */\ntype ResolvedPreviewLoading = {\n  enabled: boolean;\n  delayMs: number;\n  minVisibleMs: number;\n  timeoutMs: number;\n  injectReadySignal: boolean;\n  /** Escalation label text, or `false` for an icon-only indicator forever. */\n  label: string | false;\n  /** Delay before the escalation label fades in, from when the overlay shows. */\n  labelDelayMs: number;\n  /** Host override for the whole indicator; `undefined` uses the spinner+label. */\n  renderIndicator?: (ctx: PreviewIndicatorContext) => HTMLElement | null;\n};\n\n/**\n * Resolve `filePreview.loading` to concrete values. `false` disables the overlay\n * and injected reporter entirely; `true` / `undefined` / an object with holes\n * fall back to the documented defaults.\n */\nfunction resolvePreviewLoading(\n  loading: boolean | PreviewLoadingObject | undefined\n): ResolvedPreviewLoading {\n  if (loading === false) {\n    return {\n      enabled: false,\n      delayMs: 0,\n      minVisibleMs: 0,\n      timeoutMs: 0,\n      injectReadySignal: false,\n      label: \"Starting preview...\",\n      labelDelayMs: 2000,\n    };\n  }\n  const o = loading && typeof loading === \"object\" ? loading : undefined;\n  return {\n    enabled: true,\n    delayMs: o?.delayMs ?? 200,\n    minVisibleMs: o?.minVisibleMs ?? 300,\n    timeoutMs: o?.timeoutMs ?? 8000,\n    injectReadySignal: o?.injectReadySignal !== false,\n    // `label: false` disables the escalation text; a string overrides the default.\n    label: o?.label === false ? false : (o?.label ?? \"Starting preview...\"),\n    labelDelayMs: o?.labelDelayMs ?? 2000,\n    renderIndicator: o?.renderIndicator,\n  };\n}\n\n/** How long the overlay fade-out runs before removal; matches the CSS transition. */\nconst PREVIEW_FADE_MS = 220;\n\n/**\n * Random token tying a ready `postMessage` to the exact iframe build that\n * injected it. Collision is harmless — worst case an early overlay dismiss, not\n * a security issue — so `Math.random` is an acceptable fallback for `crypto`.\n */\nfunction makePreviewToken(): string {\n  try {\n    if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n      const a = new Uint32Array(2);\n      crypto.getRandomValues(a);\n      return a[0].toString(36) + a[1].toString(36);\n    }\n  } catch {\n    /* fall through */\n  }\n  return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);\n}\n\n/**\n * Inline reporter APPENDED (never prepended — a script before `<!doctype html>`\n * would trip quirks mode) to the srcdoc. On `window` load it waits a double\n * `requestAnimationFrame` (a cheap \"content has painted\" heuristic) then posts\n * the ready signal to the parent. The `token` is JSON-quoted so it can't break\n * out of the string literal.\n */\nfunction buildReadyReporter(token: string): string {\n  return (\n    \"\\n<script>(function(){var d=false;var t=\" +\n    JSON.stringify(token) +\n    \";function r(){if(d)return;d=true;try{parent.postMessage({persona:'artifact-preview-ready',token:t},'*');}catch(e){}}\" +\n    // Race a double-rAF (paint heuristic) against a short timeout: the parent's\n    // opaque loading overlay can make the browser treat this (process-isolated)\n    // iframe as occluded and throttle its rendering, in which case rAF callbacks\n    // stall indefinitely — the overlay would delay its own dismissal signal. The\n    // timeout keeps the signal bounded; the rAF path stays paint-accurate when\n    // frames are being produced.\n    \"function s(){if(typeof requestAnimationFrame==='function'){requestAnimationFrame(function(){requestAnimationFrame(r);});}setTimeout(r,150);}\" +\n    // Split the closing tag so this JS string can't prematurely close a host\n    // <script> if the bundle is ever inlined; the srcdoc still receives </script>.\n    \"if(document.readyState==='complete'){s();}else{window.addEventListener('load',s);}})();</scr\" +\n    \"ipt>\"\n  );\n}\n\n/** Overlay handle: the element plus a callback that fades the escalation label in. */\ntype PreviewOverlayHandle = {\n  el: HTMLElement;\n  /**\n   * Reveal the escalation label. No-op when the label is disabled\n   * (`label: false`) or a custom `renderIndicator` owns the content.\n   */\n  revealLabel: () => void;\n};\n\n/**\n * Build the preview-loading overlay content.\n *\n * Default indicator: an icon spinner with NO text (icon-first per HIG/Geist/\n * Sandpack — text-only \"loading\" is used by no reputable preview surface). The\n * optional escalation label is rendered hidden and only faded in later, from\n * `setupPreviewLoading`, once the wait crosses `labelDelayMs`.\n *\n * `renderIndicator` (host override): called once with `{ artifactId, config }`.\n * A returned element replaces the spinner + label entirely (the escalation\n * logic is skipped — the host owns the content); `null`/`undefined` or a thrown\n * error falls back to the default, mirroring the inlineActions null-falls-back\n * pattern.\n */\nfunction buildPreviewOverlay(\n  artifactId: string,\n  config: AgentWidgetConfig,\n  opts: ResolvedPreviewLoading\n): PreviewOverlayHandle {\n  const overlay = createElement(\"div\", \"persona-artifact-frame-loading\");\n\n  if (opts.renderIndicator) {\n    try {\n      const custom = opts.renderIndicator({ artifactId, config });\n      if (custom) {\n        overlay.appendChild(custom);\n        return { el: overlay, revealLabel: () => {} };\n      }\n    } catch {\n      /* fall through to the default spinner + label */\n    }\n  }\n\n  const indicator = createElement(\"div\", \"persona-artifact-frame-loading-indicator\");\n  indicator.appendChild(createSpinner());\n\n  let labelEl: HTMLElement | null = null;\n  if (opts.label !== false) {\n    // Plain, calm text — no shimmer (deliberate escalation, per the research).\n    // Hidden until revealLabel() adds the --visible modifier (opacity transition).\n    labelEl = createElement(\"div\", \"persona-artifact-frame-loading-text\");\n    labelEl.textContent = opts.label;\n    indicator.appendChild(labelEl);\n  }\n  overlay.appendChild(indicator);\n\n  return {\n    el: overlay,\n    revealLabel: () => {\n      if (labelEl) {\n        labelEl.classList.add(\"persona-artifact-frame-loading-text--visible\");\n      }\n    },\n  };\n}\n\n/**\n * Drive the preview-loading overlay for one built iframe. Plain DOM + timers, no\n * deps. Returns a teardown that removes the window/message + iframe/load\n * listeners, clears every timer, and drops the overlay immediately — called when\n * the iframe is replaced/rebuilt so the reuse path never stacks listeners.\n *\n * Dismiss rules:\n * - injection on (`token` set): dismiss on the matched `postMessage`, or on the\n *   hard timeout. The iframe `load` event is intentionally ignored — the message\n *   is strictly later and more accurate (post-DOMContentLoaded rendering).\n * - injection off (`token` null): dismiss on `load` + a double rAF, or the timeout.\n * The iframe itself is never hidden; the opaque themed overlay covers pre-paint.\n */\nfunction setupPreviewLoading(\n  frame: HTMLElement,\n  iframe: HTMLIFrameElement,\n  token: string | null,\n  opts: ResolvedPreviewLoading,\n  artifactId: string,\n  config: AgentWidgetConfig\n): () => void {\n  let overlay: HTMLElement | null = null;\n  let shownAt = 0;\n  let settled = false;\n  const timers = new Set<number>();\n  const clearTimers = () => {\n    timers.forEach((id) => clearTimeout(id));\n    timers.clear();\n  };\n  const addTimer = (fn: () => void, ms: number): void => {\n    const id = setTimeout(() => {\n      timers.delete(id);\n      fn();\n    }, ms) as unknown as number;\n    timers.add(id);\n  };\n\n  const removeOverlay = () => {\n    if (overlay && overlay.parentElement) overlay.remove();\n    overlay = null;\n  };\n  const showOverlay = () => {\n    if (overlay || settled) return;\n    const built = buildPreviewOverlay(artifactId, config, opts);\n    overlay = built.el;\n    frame.appendChild(overlay);\n    shownAt = Date.now();\n    // Escalation label timer starts NOW (overlay-visible), not from iframe\n    // creation, and lives in the same timer Set so teardown/settle clear it —\n    // no post-teardown DOM mutation. No-op when label is disabled / a custom\n    // indicator owns the content.\n    if (opts.label !== false) addTimer(built.revealLabel, opts.labelDelayMs);\n  };\n  const fadeOut = () => {\n    if (!overlay) return;\n    overlay.classList.add(\"persona-artifact-frame-loading--out\");\n    addTimer(removeOverlay, PREVIEW_FADE_MS);\n  };\n\n  const detachListeners = () => {\n    if (token !== null) {\n      if (typeof window !== \"undefined\") {\n        window.removeEventListener(\"message\", onMessage);\n      }\n    } else {\n      iframe.removeEventListener(\"load\", onLoad);\n    }\n  };\n\n  // Ready: stop listening + the delay/timeout timers, then either drop a\n  // never-shown overlay's timers or enforce minVisibleMs before fading.\n  const settle = () => {\n    if (settled) return;\n    settled = true;\n    detachListeners();\n    clearTimers();\n    if (!overlay) return;\n    const remaining = Math.max(0, opts.minVisibleMs - (Date.now() - shownAt));\n    if (remaining > 0) addTimer(fadeOut, remaining);\n    else fadeOut();\n  };\n\n  function onMessage(e: MessageEvent) {\n    if (token === null) return;\n    const d = e.data as { persona?: unknown; token?: unknown } | null;\n    if (!d || d.persona !== \"artifact-preview-ready\" || d.token !== token) return;\n    // An opaque-origin srcdoc reports its origin as the string \"null\", so origin\n    // alone is worthless — match the source window identity instead.\n    if (e.source !== iframe.contentWindow) return;\n    settle();\n  }\n  function onLoad() {\n    const raf = (cb: () => void) => {\n      if (typeof requestAnimationFrame === \"function\") {\n        requestAnimationFrame(() => cb());\n      } else {\n        setTimeout(cb, 0);\n      }\n    };\n    raf(() => raf(() => settle()));\n  }\n\n  addTimer(showOverlay, opts.delayMs);\n  addTimer(settle, opts.timeoutMs);\n  if (token !== null) {\n    if (typeof window !== \"undefined\") {\n      window.addEventListener(\"message\", onMessage);\n    }\n  } else {\n    iframe.addEventListener(\"load\", onLoad);\n  }\n\n  return () => {\n    settled = true;\n    clearTimers();\n    detachListeners();\n    removeOverlay();\n  };\n}\n\n/**\n * Render an artifact's preview body into a stable wrapper element.\n *\n * Covers every body kind the artifact pane renders:\n * - markdown artifacts → sanitized rendered markdown (or raw source when the\n *   host resolves `\"source\"` view mode)\n * - previewable file artifacts (markdown records carrying `file` meta) →\n *   sandboxed `iframe srcdoc` once complete, raw source while streaming or\n *   when `features.artifacts.filePreview` disables the preview\n * - component artifacts → registry-resolved renderer, falling back to the\n *   inspector card for unknown components or renderer failures\n *\n * `update()` re-renders in place. Two nodes are reused across updates rather\n * than rebuilt: the file-preview iframe (reused while the artifact id + source\n * are unchanged, so idle re-renders don't reload it) and the source window's\n * `<pre>`/`<code>` (only the highlighted content is replaced per streaming\n * delta, so a fixed window's scroll position and reserved height survive).\n *\n * When `ctx.bodyLayout` is set (inline path) the streaming source renders into\n * a fixed-height scroll window with optional tail-follow and edge fades, or —\n * with `streamingView: \"status\"` — a centered placeholder; the pane path\n * (no `bodyLayout`) renders content-sized exactly as before.\n */\nexport function renderArtifactPreviewBody(\n  record: PersonaArtifactRecord,\n  ctx: ArtifactPreviewContext\n): ArtifactPreviewBodyHandle {\n  const { config } = ctx;\n  const registry = ctx.registry ?? componentRegistry;\n  const layout = ctx.bodyLayout;\n\n  const md = config.markdown ? createMarkdownProcessorFromConfig(config.markdown) : null;\n  const sanitize = resolveSanitizer(config.sanitize);\n  // Degraded path (IIFE/CDN build before the `markdown-parsers.js` chunk\n  // resolves): the markdown processor falls back to escapeHtml, and the default\n  // sanitizer's own fallback is escapeHtml too, so sanitizing that output would\n  // escape a SECOND time and display literal entities (mirrors\n  // `buildPostprocessor` in ui.ts). Chat messages self-heal via the\n  // parser-ready re-render at the end of createAgentExperience, but this body\n  // only re-renders on update() — so an artifact upserted right after init\n  // would stay escaped until the next update. When a render takes the fallback,\n  // self-heal through the shared `onMarkdownParsersReady` registry — the single\n  // self-heal path every markdown surface shares — instead of an ad-hoc\n  // `loadMarkdownParsers().then(...)`. The `parserRerenderScheduled` flag keeps\n  // repeated renders from stacking subscriptions. The returned unsubscribe is\n  // intentionally ignored: the subscription is fire-once and the preview handle\n  // exposes no teardown hook to call it from.\n  let current = record;\n  let parserRerenderScheduled = false;\n  const toHtml = (text: string) => {\n    const parsersReady = getMarkdownParsersSync() !== null;\n    if (md && !parsersReady && !parserRerenderScheduled) {\n      parserRerenderScheduled = true;\n      onMarkdownParsersReady(() => render(current));\n    }\n    const raw = md ? md(text) : escapeHtml(text);\n    // escapeHtml output is already inert — only real markdown HTML is sanitized.\n    return md && parsersReady && sanitize ? sanitize(raw) : raw;\n  };\n\n  const el = createElement(\"div\", \"persona-artifact-preview-body\");\n\n  // File-preview reuse: re-appending a detached iframe reloads its srcdoc, so\n  // keep the positioned frame (wrapper + iframe + overlay) and skip rebuilding\n  // when the artifact + source are unchanged. `filePreviewLoadingCleanup` tears\n  // down the current frame's overlay state machine (listeners + timers) before a\n  // rebuild so the reuse path never stacks listeners.\n  let filePreviewFrame: HTMLElement | null = null;\n  let filePreviewKey: string | null = null;\n  let filePreviewLoadingCleanup: (() => void) | null = null;\n  const resetFilePreview = () => {\n    if (filePreviewLoadingCleanup) {\n      filePreviewLoadingCleanup();\n      filePreviewLoadingCleanup = null;\n    }\n    filePreviewFrame = null;\n    filePreviewKey = null;\n  };\n\n  // Source-window reuse: the top node appended to `el` for the source path —\n  // the scroll window on the inline path (bodyLayout set) or the bare <pre> on\n  // the pane path. Kept stable across deltas so `sourceCode`'s content can be\n  // swapped in place without resetting scroll position or reserved height.\n  let sourceRoot: HTMLElement | null = null;\n  let sourceCode: HTMLElement | null = null;\n  let sourceKey: string | null = null;\n  // Per-window \"the reader deliberately scrolled away from the tail\" latch.\n  // Set by an upward wheel / a touch drag (real intent), NEVER by a `scroll`\n  // event — growth-induced scroll is the industry-wide freeze bug we avoid.\n  // Cleared when the reader lands back near the bottom or the stream completes.\n  // Lives in this closure so it resets with the window (resetSource).\n  let escaped = false;\n  const resetSource = () => {\n    sourceRoot = null;\n    sourceCode = null;\n    sourceKey = null;\n    escaped = false;\n  };\n\n  // Status-view reuse: kept stable across streaming deltas so the \"Generating …\"\n  // animation doesn't restart on every delta.\n  let statusView: HTMLElement | null = null;\n  let statusKey: string | null = null;\n  const resetStatus = () => {\n    statusView = null;\n    statusKey = null;\n  };\n\n  const NEAR_BOTTOM_PX = 40;\n  let followRaf = 0;\n  const scheduleFrame = (cb: () => void): number => {\n    if (typeof requestAnimationFrame === \"function\") {\n      return requestAnimationFrame(() => cb());\n    }\n    return setTimeout(cb, 0) as unknown as number;\n  };\n  const isNearBottom = (sc: HTMLElement): boolean =>\n    sc.scrollHeight - sc.clientHeight - sc.scrollTop <= NEAR_BOTTOM_PX;\n  const updateFadeClasses = (sc: HTMLElement) => {\n    if (!layout) return;\n    const overflow = sc.scrollHeight - sc.clientHeight > 1;\n    const distToBottom = sc.scrollHeight - sc.clientHeight - sc.scrollTop;\n    sc.classList.toggle(\n      \"persona-artifact-fade-top\",\n      layout.fadeTop && overflow && sc.scrollTop > 1\n    );\n    sc.classList.toggle(\n      \"persona-artifact-fade-bottom\",\n      layout.fadeBottom && overflow && distToBottom > 1\n    );\n  };\n  const stickToBottom = (sc: HTMLElement) => {\n    if (followRaf) return;\n    followRaf = scheduleFrame(() => {\n      followRaf = 0;\n      sc.scrollTop = sc.scrollHeight;\n      updateFadeClasses(sc);\n    });\n  };\n\n  // Render the syntax-highlighted source. On the inline path the <pre> lives in\n  // a scroll window (`fixed` → reserved height + internal scroll + follow/fade);\n  // on the pane path it is a bare child of `el`, byte-identical to before.\n  const renderSource = (\n    text: string,\n    opts: { language?: string; path?: string } | undefined,\n    rec: PersonaArtifactRecord,\n    fixed: boolean\n  ) => {\n    const useWindow = !!layout;\n    const key = rec.id + \"|\" + (useWindow ? \"w\" : \"p\");\n    if (!sourceRoot || sourceKey !== key || sourceRoot.parentElement !== el) {\n      resetFilePreview();\n      resetStatus();\n      el.replaceChildren();\n      const pre = createElement(\"pre\", PRE_CLASS + \" persona-code-pre\");\n      const code = createElement(\"code\", \"persona-code\");\n      pre.appendChild(code);\n      if (useWindow) {\n        const scroll = createElement(\"div\", \"persona-artifact-source-window\");\n        scroll.appendChild(pre);\n        el.appendChild(scroll);\n        // Clip mode (overflow: \"clip\") is a static top-of-document window:\n        // `overflow: hidden`, no internal scroll, no tail-follow — so it wires\n        // none of the scroll/wheel/touch intent listeners below (there is\n        // nothing to scroll and nothing to unstick).\n        const clip = layout?.overflow === \"clip\";\n        if (!clip && typeof scroll.addEventListener === \"function\") {\n          // Does the window actually clip content? Gesture intent only counts\n          // while there is somewhere to scroll to.\n          const overflows = () => scroll.scrollHeight - scroll.clientHeight > 1;\n          // `scroll` fires for BOTH user scrolling and growth-induced reflow, so\n          // it never SETS the escaped latch (that misread is the freeze bug).\n          // It only clears the latch when the reader has returned to the bottom.\n          scroll.addEventListener(\n            \"scroll\",\n            () => {\n              if (isNearBottom(scroll)) escaped = false;\n              updateFadeClasses(scroll);\n            },\n            { passive: true }\n          );\n          // An upward wheel over an overflowing window is deliberate: the reader\n          // wants to look back, so stop pinning the tail (matches\n          // use-stick-to-bottom's `deltaY < 0` unstick).\n          scroll.addEventListener(\n            \"wheel\",\n            (e) => {\n              if (overflows() && e.deltaY < 0) escaped = true;\n            },\n            { passive: true }\n          );\n          // Touch drags carry no reliable direction here, so any touch move over\n          // an overflowing window is treated as taking manual control; the scroll\n          // listener re-engages follow once they flick back to the bottom.\n          scroll.addEventListener(\n            \"touchmove\",\n            () => {\n              if (overflows()) escaped = true;\n            },\n            { passive: true }\n          );\n        }\n        sourceRoot = scroll;\n      } else {\n        el.appendChild(pre);\n        sourceRoot = pre;\n      }\n      sourceCode = code;\n      sourceKey = key;\n    }\n\n    const scroll = useWindow ? sourceRoot : null;\n    if (scroll) {\n      scroll.classList.toggle(\"persona-artifact-source-window--fixed\", fixed);\n      // Clip windows carry both classes: --fixed supplies the reserved height +\n      // fill-capable detection (artifact-inline.ts), and --clip overrides its\n      // overflow-y: auto with overflow: hidden (declared later in widget.css).\n      scroll.classList.toggle(\n        \"persona-artifact-source-window--clip\",\n        fixed && layout?.overflow === \"clip\"\n      );\n    }\n    // Measure before the swap: tail-follow only when the reader was already at\n    // the bottom (don't fight a reader who scrolled up).\n    const wasNearBottom = scroll ? isNearBottom(scroll) : true;\n    // Syntax highlighting + line-number gutter. The built fragment's\n    // textContent equals `text` verbatim; the DOM is span/#text only (no\n    // innerHTML) so it stays safe regardless of `sanitize` config.\n    sourceCode!.replaceChildren(highlightCode(text, opts?.language, opts?.path));\n    if (scroll) {\n      // Follow only while streaming; the complete render keeps whatever scroll\n      // position the window ended on. Two gates stop the pin: the `escaped`\n      // latch (an explicit wheel-up / touch gesture — sharp intent detection)\n      // and the positional `wasNearBottom` fallback (covers programmatic and\n      // keyboard scrolling, which fire neither wheel nor touch). Completion\n      // clears the latch so a reused window follows the next stream from scratch.\n      const streaming = rec.status !== \"complete\";\n      if (fixed && streaming && layout?.followOutput && !escaped && wasNearBottom) {\n        stickToBottom(scroll);\n      } else if (!streaming) {\n        escaped = false;\n      }\n      updateFadeClasses(scroll);\n    }\n  };\n\n  const renderMarkdown = (text: string) => {\n    resetFilePreview();\n    resetSource();\n    resetStatus();\n    el.replaceChildren();\n    const wrap = createElement(\"div\", MARKDOWN_WRAP_CLASS);\n    wrap.innerHTML = toHtml(text);\n    el.appendChild(wrap);\n  };\n\n  // A quiet, reserved-height placeholder shown while streaming when\n  // `streamingView: \"status\"`. Reuses the same animated status the chrome uses.\n  //\n  // The status VIEW element is reused across streaming deltas (rebuilding it\n  // would restart the loading animation), but the label/detail are re-resolved\n  // and re-applied on every update — that's what makes a dynamic `statusLabel`\n  // (live counters via `{ label, detail }`) tick. `applyArtifactStatus` keeps\n  // the animated label span stable, only re-applying it when its text changes.\n  const renderStatusView = (rec: PersonaArtifactRecord) => {\n    const key = rec.id;\n    const artifactsCfg = config.features?.artifacts;\n    const resolved = resolveArtifactStatusLabel(rec, artifactsCfg, \"status-body\");\n    if (statusView && statusKey === key && statusView.parentElement === el) {\n      const textEl = statusView.querySelector<HTMLElement>(\n        \".persona-artifact-status-view-text\"\n      );\n      if (textEl) applyArtifactStatus(textEl, resolved, artifactsCfg);\n      return;\n    }\n    resetFilePreview();\n    resetSource();\n    el.replaceChildren();\n    const wrap = createElement(\"div\", \"persona-artifact-status-view\");\n    const textEl = createElement(\"div\", \"persona-artifact-status-view-text\");\n    applyArtifactStatus(textEl, resolved, artifactsCfg);\n    wrap.appendChild(textEl);\n    el.appendChild(wrap);\n    statusView = wrap;\n    statusKey = key;\n  };\n\n  const render = (rec: PersonaArtifactRecord) => {\n    current = rec;\n    const viewMode = ctx.resolveViewMode?.(rec) ?? layout?.viewMode ?? \"rendered\";\n    const fileMeta = rec.artifactType === \"markdown\" ? rec.file : undefined;\n    const isStreaming = rec.status !== \"complete\";\n    // Drop the status-label elapsed tracking once the artifact completes so the\n    // module-level map can't grow without bound (no-op for untracked ids / the\n    // pane path).\n    if (!isStreaming) clearArtifactStatusTracking(rec.id);\n    // Numeric height for the current state → fixed window (source view only).\n    // The complete state sizes the same inner window the streaming state does\n    // (never the padded body wrapper), so a source→source completion is\n    // geometry-identical and shifts nothing.\n    const stateHeight = isStreaming\n      ? layout?.streamingHeight\n      : layout?.completeHeight;\n    const fixed = !!layout && typeof stateHeight === \"number\";\n\n    // Streaming status placeholder (inline only; gated by bodyLayout).\n    if (layout?.streamingView === \"status\" && isStreaming) {\n      renderStatusView(rec);\n      return;\n    }\n\n    // Previewable file artifact branch (markdown artifact carrying `file` meta).\n    if (fileMeta) {\n      const source = extractFileSource(rec.markdown ?? \"\");\n      const kind = fileKindOf(fileMeta);\n      const previewEnabled = config.features?.artifacts?.filePreview?.enabled !== false;\n      const wantIframe =\n        !isStreaming &&\n        viewMode === \"rendered\" &&\n        previewEnabled &&\n        (kind === \"html\" || kind === \"svg\");\n\n      if (wantIframe) {\n        // NUL separator: cannot appear in an id, so ids and sources never\n        // collide across the boundary.\n        const key = rec.id + \"\\u0000\" + source;\n        // Reuse the existing frame when nothing changed so idle re-renders don't\n        // reload the iframe (re-appending a detached iframe reloads its srcdoc)\n        // or restart its loading overlay / stack a second message listener.\n        if (\n          filePreviewFrame &&\n          filePreviewKey === key &&\n          filePreviewFrame.parentElement === el\n        ) {\n          return;\n        }\n        resetSource();\n        resetStatus();\n        // Tear down any prior frame's overlay state machine before rebuilding.\n        resetFilePreview();\n        el.replaceChildren();\n\n        const fp = config.features?.artifacts?.filePreview;\n        // allow-same-origin would give agent-written srcdoc content the host\n        // page's origin; require the explicit dangerouslyAllowSameOrigin flag.\n        let sandbox = fp?.iframeSandbox ?? \"allow-scripts\";\n        if (!fp?.dangerouslyAllowSameOrigin && sandbox.includes(\"allow-same-origin\")) {\n          console.warn(\n            \"[AgentWidget] Stripped allow-same-origin from filePreview.iframeSandbox: it lets artifact content run with the page origin. Set filePreview.dangerouslyAllowSameOrigin to keep it.\"\n          );\n          sandbox = sandbox\n            .split(/\\s+/)\n            .filter((token) => token && token !== \"allow-same-origin\")\n            .join(\" \");\n        }\n        const loadingOpts = resolvePreviewLoading(fp?.loading);\n\n        // Positioned wrapper hosts the iframe + the (absolute) loading overlay;\n        // `el` is display: contents, so the iframe needs its own positioning\n        // context. Geometry (frame owns the height, iframe is 100% of it) lives\n        // in widget.css.\n        const frame = createElement(\"div\", \"persona-artifact-frame\");\n        const iframe = createElement(\"iframe\", \"persona-artifact-iframe\");\n        iframe.setAttribute(\"sandbox\", sandbox);\n        iframe.setAttribute(\"data-artifact-id\", rec.id);\n\n        // Assign srcdoc as a property (never innerHTML / marked / DOMPurify):\n        // the sandbox (no allow-same-origin → opaque origin) is the isolation\n        // boundary. The ready reporter is appended (never prepended) so the\n        // document's doctype stays first.\n        let token: string | null = null;\n        if (loadingOpts.enabled && loadingOpts.injectReadySignal) {\n          token = makePreviewToken();\n          iframe.srcdoc = source + buildReadyReporter(token);\n        } else {\n          iframe.srcdoc = source;\n        }\n\n        frame.appendChild(iframe);\n        el.appendChild(frame);\n\n        if (loadingOpts.enabled) {\n          filePreviewLoadingCleanup = setupPreviewLoading(\n            frame,\n            iframe,\n            token,\n            loadingOpts,\n            rec.id,\n            config\n          );\n        }\n\n        filePreviewFrame = frame;\n        filePreviewKey = key;\n        return;\n      }\n\n      // Not rendering an iframe: drop the cached one.\n      resetFilePreview();\n\n      // Complete markdown file → existing markdown pipeline (sanitized).\n      if (!isStreaming && kind === \"markdown\" && viewMode === \"rendered\") {\n        renderMarkdown(source);\n        return;\n      }\n\n      // Streaming, source view, or non-previewable kind → raw source in a <pre>,\n      // syntax-highlighted by the file's language / path.\n      renderSource(source, { language: fileMeta.language, path: fileMeta.path }, rec, fixed);\n      return;\n    }\n\n    // Non-file markdown.\n    if (rec.artifactType === \"markdown\") {\n      if (viewMode === \"source\") {\n        resetFilePreview();\n        renderSource(rec.markdown ?? \"\", undefined, rec, fixed);\n        return;\n      }\n      renderMarkdown(rec.markdown ?? \"\");\n      return;\n    }\n\n    // Component artifact.\n    resetFilePreview();\n    resetSource();\n    resetStatus();\n    el.replaceChildren();\n    const renderer = rec.component ? registry.get(rec.component) : undefined;\n    if (renderer) {\n      const stubMessage: AgentWidgetMessage = {\n        id: rec.id,\n        role: \"assistant\",\n        content: \"\",\n        createdAt: new Date().toISOString(),\n      };\n      const componentCtx: ComponentContext = {\n        message: stubMessage,\n        config,\n        updateProps: () => {},\n      };\n      try {\n        const node = renderer(rec.props ?? {}, componentCtx);\n        if (node) {\n          el.appendChild(node);\n          return;\n        }\n      } catch {\n        /* fall through to the inspector card */\n      }\n    }\n    el.appendChild(fallbackComponentCard(rec));\n  };\n\n  render(record);\n\n  return {\n    el,\n    update(rec: PersonaArtifactRecord) {\n      render(rec);\n    },\n  };\n}\n","import { Idiomorph } from \"idiomorph\";\n\nexport type MorphOptions = {\n  preserveTypingAnimation?: boolean;\n};\n\n/**\n * Morph a container's contents using idiomorph with chat-widget-specific\n * preservation rules for typing indicators.\n *\n * Action buttons are matched by their `id` attribute (set to `actions-{messageId}`)\n * so idiomorph updates them in place rather than recreating them.\n */\nexport const morphMessages = (\n  container: HTMLElement,\n  newContent: HTMLElement,\n  options: MorphOptions = {}\n): void => {\n  const { preserveTypingAnimation = true } = options;\n\n  Idiomorph.morph(container, newContent.innerHTML, {\n    morphStyle: \"innerHTML\",\n    callbacks: {\n      beforeNodeMorphed(oldNode: Node, newNode: Node): boolean | void {\n        if (!(oldNode instanceof HTMLElement)) return;\n\n        // Preserve typing indicator dots to maintain animation continuity\n        // Also preserve elements with data-preserve-animation attribute for custom loading indicators\n        if (preserveTypingAnimation) {\n          if (oldNode.classList.contains(\"persona-animate-typing\")) {\n            return false;\n          }\n          // Plugins actively mutating a node (e.g. glyph-cycle's tick loop)\n          // opt out of morph entirely via this attribute. Unlike\n          // `data-preserve-animation`, this is honored regardless of whether\n          // the new DOM carries the attribute: it's a runtime-only marker.\n          if (oldNode.hasAttribute(\"data-preserve-runtime\")) {\n            return false;\n          }\n          // The live tool-elapsed span's text is owned by the 100ms global\n          // timer in ui.ts (it rewrites `now - startedAt` on a fixed cadence).\n          // Re-morphing it to the render-time value fights that timer, so while\n          // a tool stays active across frequent re-renders the \"<0.1s\" boundary\n          // flickers. Preserve it while the SAME tool is still live (matching\n          // `data-tool-elapsed`, i.e. startedAt); allow the morph once the tool\n          // completes (new node drops the marker → final static duration) or the\n          // slot is reused by another tool (marker value changed).\n          if (oldNode.hasAttribute(\"data-tool-elapsed\")) {\n            const sameLiveTool =\n              newNode instanceof HTMLElement &&\n              newNode.getAttribute(\"data-tool-elapsed\") ===\n                oldNode.getAttribute(\"data-tool-elapsed\");\n            if (sameLiveTool) {\n              return false;\n            }\n            return;\n          }\n          if (oldNode.hasAttribute(\"data-preserve-animation\")) {\n            // Allow morph when the new node drops the attribute (e.g. tool completed)\n            if (newNode instanceof HTMLElement && !newNode.hasAttribute(\"data-preserve-animation\")) {\n              return;\n            }\n            // Allow morph when content has meaningfully changed (e.g. tool name arrived)\n            if (newNode instanceof HTMLElement && newNode.hasAttribute(\"data-preserve-animation\")) {\n              const oldText = oldNode.textContent ?? \"\";\n              const newText = newNode.textContent ?? \"\";\n              if (oldText !== newText) {\n                return;\n              }\n            }\n            return false;\n          }\n        }\n      },\n    },\n  });\n};\n","/**\n * Normalize text pulled from a native browser selection before it is written to\n * the clipboard.\n *\n * When a user triple-clicks a chat bubble and presses Ctrl/Cmd-C, the browser\n * serializes the DOM selection itself. Markdown is rendered into block-level\n * elements (`<p>`, `<li>`, `<pre>`, …), and browsers emit surrounding newlines\n * for those blocks — so copying a single message drags along stray leading\n * blank lines and a trailing newline that the user never visually selected.\n *\n * This trims the outer whitespace so the clipboard matches the visible\n * selection, while preserving:\n *   - interior newlines (a multi-paragraph or multi-message selection keeps its\n *     line breaks), and\n *   - leading indentation on the first line (e.g. copied code keeps its indent;\n *     only fully-blank leading lines are dropped).\n */\nexport const normalizeCopiedSelectionText = (text: string): string =>\n  text.replace(/^\\n+/, \"\").replace(/\\s+$/, \"\");\n","/**\n * Pure state machine for composer message-history navigation (Up/Down arrows).\n *\n * Mirrors the shell / Slack convention: pressing Up recalls previously sent\n * user messages for re-entry or editing; Down walks back toward the present\n * and restores the in-progress draft once you page past the newest entry.\n *\n * Kept free of DOM access so it can be unit-tested in the Node test env. The\n * UI layer (`ui.ts`) supplies caret information and applies the returned value\n * to the textarea.\n */\n\nexport interface ComposerHistoryState {\n  /** Index into the history list, or -1 when not navigating. */\n  index: number;\n  /** The user's in-progress text, saved when navigation begins. */\n  draft: string;\n}\n\nexport const INITIAL_HISTORY_STATE: ComposerHistoryState = {\n  index: -1,\n  draft: \"\"\n};\n\nexport interface ComposerHistoryInput {\n  direction: \"up\" | \"down\";\n  /** Previously sent user-message texts, oldest first. */\n  history: string[];\n  /** Current textarea value (saved as the draft when navigation begins). */\n  currentValue: string;\n  /** True when the caret sits at the very start of the textarea. */\n  atStart: boolean;\n  state: ComposerHistoryState;\n}\n\nexport interface ComposerHistoryResult {\n  /** Whether the key was consumed (caller should preventDefault). */\n  handled: boolean;\n  /** New textarea value to apply: only present when it should change. */\n  value?: string;\n  /** Next navigation state. */\n  state: ComposerHistoryState;\n}\n\n/**\n * Compute the next navigation state for an Up/Down key press.\n *\n * - **Up** enters history only from the top boundary (`atStart`), then keeps\n *   stepping toward older messages on each subsequent press while navigating.\n * - **Down** only acts while already navigating, stepping toward newer messages\n *   and finally restoring the saved draft once it walks past the newest entry.\n */\nexport function navigateComposerHistory(\n  input: ComposerHistoryInput\n): ComposerHistoryResult {\n  const { direction, history, currentValue, atStart, state } = input;\n  const inHistory = state.index !== -1;\n\n  if (history.length === 0) {\n    return { handled: false, state };\n  }\n\n  if (direction === \"up\") {\n    // Only hijack Up from the top boundary so normal multi-line cursor\n    // movement keeps working until the user is actually cycling history.\n    if (!inHistory && !atStart) {\n      return { handled: false, state };\n    }\n\n    if (!inHistory) {\n      // First step: stash the draft and jump to the newest entry.\n      const index = history.length - 1;\n      return {\n        handled: true,\n        value: history[index],\n        state: { index, draft: currentValue }\n      };\n    }\n\n    if (state.index > 0) {\n      const index = state.index - 1;\n      return {\n        handled: true,\n        value: history[index],\n        state: { index, draft: state.draft }\n      };\n    }\n\n    // Already at the oldest entry: consume the key but don't change.\n    return { handled: true, state };\n  }\n\n  // direction === \"down\": only meaningful while navigating history.\n  if (!inHistory) {\n    return { handled: false, state };\n  }\n\n  if (state.index < history.length - 1) {\n    const index = state.index + 1;\n    return {\n      handled: true,\n      value: history[index],\n      state: { index, draft: state.draft }\n    };\n  }\n\n  // Stepped past the newest entry: restore the saved draft and exit.\n  return {\n    handled: true,\n    value: state.draft,\n    state: { ...INITIAL_HISTORY_STATE }\n  };\n}\n","/**\n * Message-level fingerprint cache for skipping re-renders of unchanged messages.\n *\n * During streaming, every SSE chunk triggers renderMessagesWithPluginsImpl which\n * rebuilds ALL message bubbles. The fingerprint cache lets us skip rebuilding\n * messages whose content hasn't changed, so only the actively streaming message\n * is re-rendered each cycle.\n */\n\nexport type FingerprintableMessage = {\n  id: string;\n  role: string;\n  content: string;\n  streaming?: boolean;\n  voiceProcessing?: boolean;\n  variant?: string;\n  rawContent?: string;\n  llmContent?: string;\n  approval?: { status?: string; [key: string]: unknown };\n  toolCall?: {\n    status?: string;\n    chunks?: string[];\n    args?: unknown;\n    [key: string]: unknown;\n  };\n  reasoning?: { chunks?: string[]; status?: string; [key: string]: unknown };\n  contentParts?: unknown[];\n  stopReason?: string;\n};\n\nexport type MessageCacheEntry = {\n  fingerprint: string;\n  wrapper: HTMLElement;\n};\n\nexport type MessageCache = Map<string, MessageCacheEntry>;\n\n/**\n * Compute a fast fingerprint for a message to detect changes.\n * Uses string concatenation with a delimiter rather than JSON.stringify for performance.\n * The configVersion parameter ensures cache invalidation when widget config changes.\n */\nexport function computeMessageFingerprint(\n  message: FingerprintableMessage,\n  configVersion: number\n): string {\n  return [\n    message.id,\n    message.role,\n    message.content?.length ?? 0,\n    message.content?.slice(-32) ?? \"\",\n    message.streaming ? \"1\" : \"0\",\n    // voiceProcessing flips true→false on transcript finalize, usually with the\n    // SAME text — so without it the cache would reuse the live transcribing/\n    // thinking bubble (waveform/dots) for the finalized message.\n    message.voiceProcessing ? \"1\" : \"0\",\n    message.variant ?? \"\",\n    message.rawContent?.length ?? 0,\n    message.llmContent?.length ?? 0,\n    message.approval?.status ?? \"\",\n    message.toolCall?.status ?? \"\",\n    message.toolCall?.name ?? \"\",\n    message.toolCall?.chunks?.length ?? 0,\n    message.toolCall?.chunks?.[message.toolCall.chunks.length - 1]?.slice(-32) ?? \"\",\n    typeof message.toolCall?.args === \"string\"\n      ? message.toolCall.args.length\n      : message.toolCall?.args\n        ? JSON.stringify(message.toolCall.args).length\n        : 0,\n    message.reasoning?.chunks?.length ?? 0,\n    message.reasoning?.chunks?.[message.reasoning.chunks.length - 1]?.length ?? 0,\n    message.reasoning?.chunks?.[message.reasoning.chunks.length - 1]?.slice(-32) ?? \"\",\n    message.contentParts?.length ?? 0,\n    message.stopReason ?? \"\",\n    configVersion,\n  ].join(\"\\x00\");\n}\n\n/**\n * Create a new message cache instance.\n */\nexport function createMessageCache(): MessageCache {\n  return new Map();\n}\n\n/**\n * Look up a cached wrapper for a message. Returns the cached wrapper\n * if the fingerprint matches, or null if the message needs re-rendering.\n */\nexport function getCachedWrapper(\n  cache: MessageCache,\n  messageId: string,\n  fingerprint: string\n): HTMLElement | null {\n  const entry = cache.get(messageId);\n  if (entry && entry.fingerprint === fingerprint) {\n    return entry.wrapper;\n  }\n  return null;\n}\n\n/**\n * Store a rendered wrapper in the cache.\n */\nexport function setCachedWrapper(\n  cache: MessageCache,\n  messageId: string,\n  fingerprint: string,\n  wrapper: HTMLElement\n): void {\n  cache.set(messageId, { fingerprint, wrapper });\n}\n\n/**\n * Remove cache entries for messages that no longer exist.\n * Call after each render pass with the current message IDs.\n */\nexport function pruneCache(\n  cache: MessageCache,\n  activeMessageIds: Set<string>\n): void {\n  for (const key of cache.keys()) {\n    if (!activeMessageIds.has(key)) {\n      cache.delete(key);\n    }\n  }\n}\n","export type FollowStateAction = \"none\" | \"pause\" | \"resume\";\n\nexport type FollowStateController = {\n  isFollowing: () => boolean;\n  pause: () => boolean;\n  resume: () => boolean;\n};\n\nexport type FollowStateScrollInput = {\n  following: boolean;\n  currentScrollTop: number;\n  lastScrollTop: number;\n  nearBottom: boolean;\n  userScrollThreshold: number;\n  isAutoScrolling?: boolean;\n  pauseOnUpwardScroll?: boolean;\n  pauseWhenAwayFromBottom?: boolean;\n  resumeRequiresDownwardScroll?: boolean;\n};\n\nexport type FollowStateWheelInput = {\n  following: boolean;\n  deltaY: number;\n  nearBottom?: boolean;\n  resumeWhenNearBottom?: boolean;\n};\n\nexport function createFollowStateController(initiallyFollowing = true): FollowStateController {\n  let following = initiallyFollowing;\n\n  return {\n    isFollowing: () => following,\n    pause: () => {\n      if (!following) return false;\n      following = false;\n      return true;\n    },\n    resume: () => {\n      if (following) return false;\n      following = true;\n      return true;\n    }\n  };\n}\n\nexport function getScrollBottomOffset(element: Pick<HTMLElement, \"scrollHeight\" | \"clientHeight\">): number {\n  return Math.max(0, element.scrollHeight - element.clientHeight);\n}\n\nexport function isElementNearBottom(\n  element: Pick<HTMLElement, \"scrollTop\" | \"scrollHeight\" | \"clientHeight\">,\n  threshold: number\n): boolean {\n  return getScrollBottomOffset(element) - element.scrollTop <= threshold;\n}\n\nexport function resolveFollowStateFromScroll(\n  input: FollowStateScrollInput\n): { action: FollowStateAction; delta: number; nextLastScrollTop: number } {\n  const {\n    following,\n    currentScrollTop,\n    lastScrollTop,\n    nearBottom,\n    userScrollThreshold,\n    isAutoScrolling = false,\n    pauseOnUpwardScroll = false,\n    pauseWhenAwayFromBottom = true,\n    resumeRequiresDownwardScroll = false\n  } = input;\n\n  const delta = currentScrollTop - lastScrollTop;\n\n  if (isAutoScrolling || Math.abs(delta) < userScrollThreshold) {\n    return { action: \"none\", delta, nextLastScrollTop: currentScrollTop };\n  }\n\n  if (!following && nearBottom && (!resumeRequiresDownwardScroll || delta > 0)) {\n    return { action: \"resume\", delta, nextLastScrollTop: currentScrollTop };\n  }\n\n  if (following && pauseOnUpwardScroll && delta < 0) {\n    return { action: \"pause\", delta, nextLastScrollTop: currentScrollTop };\n  }\n\n  if (following && pauseWhenAwayFromBottom && !nearBottom) {\n    return { action: \"pause\", delta, nextLastScrollTop: currentScrollTop };\n  }\n\n  return { action: \"none\", delta, nextLastScrollTop: currentScrollTop };\n}\n\nexport function resolveFollowStateFromWheel(\n  input: FollowStateWheelInput\n): FollowStateAction {\n  const {\n    following,\n    deltaY,\n    nearBottom = false,\n    resumeWhenNearBottom = false\n  } = input;\n\n  if (following && deltaY < 0) {\n    return \"pause\";\n  }\n\n  if (!following && resumeWhenNearBottom && deltaY > 0 && nearBottom) {\n    return \"resume\";\n  }\n\n  return \"none\";\n}\n\nexport type SelectionLike = Pick<Selection, \"isCollapsed\"> & {\n  anchorNode: Node | null;\n  focusNode: Node | null;\n};\n\n/**\n * True when the user has a non-collapsed text selection that touches the\n * given container. Used to pause auto-follow while the user is selecting\n * transcript text, so the streaming scroll doesn't drag content out from\n * under an in-progress drag-selection.\n */\nexport function hasSelectionWithin(\n  selection: SelectionLike | null,\n  container: Pick<Node, \"contains\">\n): boolean {\n  if (!selection || selection.isCollapsed) return false;\n  return (\n    container.contains(selection.anchorNode) ||\n    container.contains(selection.focusNode)\n  );\n}\n\nexport type AnchorScrollInput = {\n  /** Top of the anchored user message, relative to the scroll content. */\n  anchorOffsetTop: number;\n  /** Desired gap kept between the anchored message and the viewport top. */\n  topOffset: number;\n  /** clientHeight of the scroll container. */\n  viewportHeight: number;\n  /** scrollHeight of the scroll container, excluding any anchor spacer. */\n  contentHeight: number;\n};\n\n/**\n * Geometry for anchor-top (\"pin the sent user message near the viewport\n * top\") scrolling. The spacer height is the extra scrollable room needed so\n * the target scroll position is reachable before the streamed response has\n * grown tall enough to provide it naturally.\n */\nexport function computeAnchorScrollState(input: AnchorScrollInput): {\n  targetScrollTop: number;\n  spacerHeight: number;\n} {\n  const targetScrollTop = Math.max(0, input.anchorOffsetTop - input.topOffset);\n  const spacerHeight = Math.max(\n    0,\n    targetScrollTop + input.viewportHeight - input.contentHeight\n  );\n  return { targetScrollTop, spacerHeight };\n}\n\n/**\n * Shrink-only spacer update: as streamed content grows beneath the anchored\n * message, the spacer gives back exactly that much room so total scroll\n * height stays constant and nothing jumps. Never grows after anchoring.\n */\nexport function computeShrunkSpacerHeight(input: {\n  initialSpacerHeight: number;\n  contentHeightAtAnchor: number;\n  currentContentHeight: number;\n}): number {\n  const growth = Math.max(\n    0,\n    input.currentContentHeight - input.contentHeightAtAnchor\n  );\n  return Math.max(0, input.initialSpacerHeight - growth);\n}\n","import type {\n  AgentWidgetMessage,\n  AgentWidgetStreamAnimationBuffer,\n  AgentWidgetStreamAnimationBuiltinType,\n  AgentWidgetStreamAnimationFeature,\n  AgentWidgetStreamAnimationPlaceholder,\n  AgentWidgetStreamAnimationType,\n  StreamAnimationPlugin,\n} from \"../types\";\n\nexport type ResolvedStreamAnimation = {\n  type: AgentWidgetStreamAnimationType;\n  placeholder: AgentWidgetStreamAnimationPlaceholder;\n  speed: number;\n  duration: number;\n  buffer: AgentWidgetStreamAnimationBuffer;\n};\n\nconst DEFAULT_STREAM_ANIMATION: ResolvedStreamAnimation = {\n  type: \"none\",\n  placeholder: \"none\",\n  speed: 120,\n  duration: 1800,\n  buffer: \"none\",\n};\n\n/** Default tags whose text descendants are not wrapped. Plugins can override. */\nconst DEFAULT_SKIP_TAGS = [\"pre\", \"code\", \"a\", \"script\", \"style\"];\n\nexport const resolveStreamAnimation = (\n  feature: AgentWidgetStreamAnimationFeature | undefined\n): ResolvedStreamAnimation => ({\n  type: feature?.type ?? DEFAULT_STREAM_ANIMATION.type,\n  placeholder: feature?.placeholder ?? DEFAULT_STREAM_ANIMATION.placeholder,\n  speed: feature?.speed ?? DEFAULT_STREAM_ANIMATION.speed,\n  duration: feature?.duration ?? DEFAULT_STREAM_ANIMATION.duration,\n  buffer: feature?.buffer ?? DEFAULT_STREAM_ANIMATION.buffer,\n});\n\n/* ============================================================\n   Plugin registry\n   ============================================================ */\n\n/**\n * Built-in animations ship with the core widget: CSS lives in widget.css\n * and no subpath import is required. They register automatically.\n *\n * Other animations (`letter-rise`, `word-fade`, `wipe`, `glyph-cycle`) are\n * tree-shakeable subpath plugins: consumers import them from\n * `@runtypelabs/persona/animations/<name>` and they auto-register on load.\n */\nconst BUILTIN_PLUGINS: StreamAnimationPlugin[] = [\n  {\n    name: \"typewriter\",\n    containerClass: \"persona-stream-typewriter\",\n    wrap: \"char\",\n    useCaret: true,\n  },\n  {\n    name: \"pop-bubble\",\n    bubbleClass: \"persona-stream-pop\",\n    wrap: \"none\",\n  },\n  {\n    name: \"letter-rise\",\n    containerClass: \"persona-stream-letter-rise\",\n    wrap: \"char\",\n  },\n  {\n    name: \"word-fade\",\n    containerClass: \"persona-stream-word-fade\",\n    wrap: \"word\",\n  },\n];\n\n/**\n * Global registry populated by:\n * - the core built-ins below (always available)\n * - `registerStreamAnimationPlugin()` calls from subpath animation modules\n *   (invoked automatically when consumers `import` them)\n * - IIFE bundle's bootstrap code (pre-registers all built-ins for script-tag\n *   consumers)\n */\nconst globalRegistry = new Map<string, StreamAnimationPlugin>();\nfor (const plugin of BUILTIN_PLUGINS) globalRegistry.set(plugin.name, plugin);\n\n/**\n * Register a custom stream animation plugin globally. Subsequent widget\n * instances can reference the plugin by `name` in `features.streamAnimation.type`.\n * Per-widget plugin overrides via `features.streamAnimation.plugins` take\n * precedence over the global registry.\n */\nexport const registerStreamAnimationPlugin = (plugin: StreamAnimationPlugin): void => {\n  globalRegistry.set(plugin.name, plugin);\n};\n\nexport const unregisterStreamAnimationPlugin = (name: string): void => {\n  // Built-ins are preserved; only external plugins can be unregistered.\n  if (BUILTIN_PLUGINS.some((p) => p.name === name)) return;\n  globalRegistry.delete(name);\n};\n\nexport const listRegisteredStreamAnimations = (): string[] =>\n  Array.from(globalRegistry.keys());\n\n/**\n * Resolve the plugin for a given type. Per-instance overrides take precedence\n * over the global registry. Returns null for `\"none\"` or unknown types.\n */\nexport const resolveStreamAnimationPlugin = (\n  type: AgentWidgetStreamAnimationType,\n  overrides?: Record<string, StreamAnimationPlugin>\n): StreamAnimationPlugin | null => {\n  if (type === \"none\") return null;\n  if (overrides && Object.prototype.hasOwnProperty.call(overrides, type)) {\n    return overrides[type] ?? null;\n  }\n  return globalRegistry.get(type) ?? null;\n};\n\n/* ============================================================\n   Buffering\n   ============================================================ */\n\n/**\n * Apply content buffering to hide in-progress words or lines during streaming.\n * Custom strategies via `plugin.bufferContent` take precedence over `buffer`.\n */\nexport const applyStreamBuffer = (\n  content: string,\n  buffer: AgentWidgetStreamAnimationBuffer,\n  plugin: StreamAnimationPlugin | null,\n  message: AgentWidgetMessage,\n  streaming: boolean\n): string => {\n  if (!streaming) return content;\n  if (plugin?.bufferContent) return plugin.bufferContent(content, message);\n  if (!content) return content;\n  if (buffer === \"word\") {\n    const lastSpace = content.search(/\\s(?=\\S*$)/);\n    if (lastSpace < 0) return \"\";\n    return content.slice(0, lastSpace);\n  }\n  if (buffer === \"line\") {\n    const lastNewline = content.lastIndexOf(\"\\n\");\n    if (lastNewline < 0) return \"\";\n    return content.slice(0, lastNewline);\n  }\n  return content;\n};\n\n/* ============================================================\n   Wrapping\n   ============================================================ */\n\nconst makeCharSpan = (\n  doc: Document,\n  ch: string,\n  messageId: string,\n  index: number\n): HTMLElement => {\n  const span = doc.createElement(\"span\");\n  span.className = \"persona-stream-char\";\n  span.id = `stream-c-${messageId}-${index}`;\n  span.style.setProperty(\"--char-index\", String(index));\n  span.textContent = ch;\n  return span;\n};\n\nconst makeWordSpan = (\n  doc: Document,\n  word: string,\n  messageId: string,\n  index: number\n): HTMLElement => {\n  const span = doc.createElement(\"span\");\n  span.className = \"persona-stream-word\";\n  span.id = `stream-w-${messageId}-${index}`;\n  span.style.setProperty(\"--word-index\", String(index));\n  span.textContent = word;\n  return span;\n};\n\nconst WHITESPACE_RE = /\\s/;\n\nconst shouldSkipSubtree = (node: Node, skipTags: Set<string>): boolean => {\n  let current: Node | null = node.parentNode;\n  while (current) {\n    if (current.nodeType === 1) {\n      const el = current as Element;\n      if (skipTags.has(el.tagName.toLowerCase())) return true;\n    }\n    current = current.parentNode;\n  }\n  return false;\n};\n\nconst wrapTextNodeChars = (\n  textNode: Text,\n  messageId: string,\n  counterRef: { value: number }\n): void => {\n  const doc = textNode.ownerDocument;\n  const parent = textNode.parentNode;\n  if (!doc || !parent) return;\n  const text = textNode.nodeValue ?? \"\";\n  if (!text) return;\n  const fragment = doc.createDocumentFragment();\n  let i = 0;\n  while (i < text.length) {\n    if (WHITESPACE_RE.test(text[i])) {\n      // Keep whitespace as a plain text node so the browser preserves natural\n      // word-break opportunities between words. `display: inline-block` spans\n      // swallow single-space content, so wrapping whitespace would collapse\n      // the spaces and break line wrapping.\n      let j = i;\n      while (j < text.length && WHITESPACE_RE.test(text[j])) j += 1;\n      fragment.appendChild(doc.createTextNode(text.slice(i, j)));\n      i = j;\n    } else {\n      // Wrap each run of non-whitespace chars in a `white-space: nowrap`\n      // group so the browser doesn't break lines between individual char\n      // spans mid-word. Word boundaries (whitespace) stay as plain text\n      // nodes between groups, preserving natural line-break opportunities.\n      const group = doc.createElement(\"span\");\n      group.className = \"persona-stream-word-group\";\n      let j = i;\n      while (j < text.length && !WHITESPACE_RE.test(text[j])) {\n        group.appendChild(makeCharSpan(doc, text[j], messageId, counterRef.value));\n        counterRef.value += 1;\n        j += 1;\n      }\n      fragment.appendChild(group);\n      i = j;\n    }\n  }\n  parent.replaceChild(fragment, textNode);\n};\n\nconst wrapTextNodeWords = (\n  textNode: Text,\n  messageId: string,\n  counterRef: { value: number }\n): void => {\n  const doc = textNode.ownerDocument;\n  const parent = textNode.parentNode;\n  if (!doc || !parent) return;\n  const text = textNode.nodeValue ?? \"\";\n  if (!text) return;\n  const fragment = doc.createDocumentFragment();\n  const tokens = text.split(/(\\s+)/);\n  for (const token of tokens) {\n    if (!token) continue;\n    if (/^\\s+$/.test(token)) {\n      fragment.appendChild(doc.createTextNode(token));\n    } else {\n      fragment.appendChild(makeWordSpan(doc, token, messageId, counterRef.value));\n      counterRef.value += 1;\n    }\n  }\n  parent.replaceChild(fragment, textNode);\n};\n\n/**\n * Wrap plain-text nodes in the sanitized markdown HTML with per-char or per-word\n * spans suitable for staggered CSS animations. Skips descendants of `<pre>`,\n * `<code>`, and `<a>` so code blocks stay legible and link click targets stay intact.\n *\n * Each wrapped span carries a stable `id` (`stream-c-{messageId}-{N}` or\n * `stream-w-{messageId}-{N}`) so idiomorph preserves existing spans across\n * token-by-token re-renders: animations on already-streamed characters never\n * restart.\n */\nexport const wrapStreamAnimation = (\n  html: string,\n  mode: \"char\" | \"word\",\n  messageId: string,\n  options?: { skipTags?: string[]; startIndex?: number }\n): string => {\n  if (!html) return html;\n  if (typeof document === \"undefined\") return html;\n\n  const scratch = document.createElement(\"div\");\n  scratch.innerHTML = html;\n\n  const skipTags = new Set((options?.skipTags ?? DEFAULT_SKIP_TAGS).map((t) => t.toLowerCase()));\n  const walker = document.createTreeWalker(scratch, NodeFilter.SHOW_TEXT, null);\n  const textNodes: Text[] = [];\n  let node = walker.nextNode();\n  while (node) {\n    if (!shouldSkipSubtree(node, skipTags)) {\n      textNodes.push(node as Text);\n    }\n    node = walker.nextNode();\n  }\n\n  // `startIndex` lets callers number spans by their absolute position in a\n  // larger string, even when only a slice is being wrapped. The peek banner\n  // uses this so per-char span IDs stay stable as the trailing-100-char\n  // window shifts each chunk: idiomorph then preserves animations on\n  // already-revealed chars instead of restarting them.\n  const counterRef = { value: options?.startIndex ?? 0 };\n  const wrap = mode === \"char\" ? wrapTextNodeChars : wrapTextNodeWords;\n  for (const textNode of textNodes) {\n    wrap(textNode, messageId, counterRef);\n  }\n\n  return scratch.innerHTML;\n};\n\n/* ============================================================\n   Supporting helpers\n   ============================================================ */\n\n/**\n * Build the caret element for `typewriter` mode. Carries\n * `data-preserve-animation` so idiomorph keeps the blink running across\n * token re-renders.\n */\nexport const createStreamCaret = (doc: Document = document): HTMLElement => {\n  const caret = doc.createElement(\"span\");\n  caret.className = \"persona-stream-caret\";\n  caret.setAttribute(\"aria-hidden\", \"true\");\n  caret.setAttribute(\"data-preserve-animation\", \"stream-caret\");\n  return caret;\n};\n\n/**\n * Shimmer placeholder shown before the first token arrives, and, when the\n * `\"line\"` buffer strategy is active, reshown between lines. A single\n * full-width bar; we don't know ahead of time how wide the next line will be,\n * so committing to one width avoids implying structure the stream won't match.\n */\nexport const createSkeletonPlaceholder = (doc: Document = document): HTMLElement => {\n  const wrapper = doc.createElement(\"div\");\n  wrapper.className = \"persona-stream-skeleton\";\n  wrapper.setAttribute(\"data-preserve-animation\", \"stream-skeleton\");\n  wrapper.setAttribute(\"aria-hidden\", \"true\");\n  const line = doc.createElement(\"div\");\n  line.className = \"persona-stream-skeleton-line\";\n  wrapper.appendChild(line);\n  return wrapper;\n};\n\n/* ============================================================\n   Plugin style + attach lifecycle\n   ============================================================ */\n\n/**\n * Track which plugins have injected their CSS into a given root already.\n * Prevents duplicate <style> tags across widget instances or re-renders.\n */\nconst injectedStyleRoots = new WeakMap<HTMLElement | ShadowRoot, Set<string>>();\n\nexport const injectPluginStyles = (\n  plugin: StreamAnimationPlugin,\n  root: HTMLElement | ShadowRoot\n): void => {\n  if (!plugin.styles) return;\n  let names = injectedStyleRoots.get(root);\n  if (!names) {\n    names = new Set();\n    injectedStyleRoots.set(root, names);\n  }\n  if (names.has(plugin.name)) {\n    // The tracking Set says we injected this plugin's styles, but the actual\n    // <style> node may have been removed (e.g. host cleared via `innerHTML = \"\"`\n    // during widget re-init). Fall through and re-inject if the tag is gone.\n    const escaped = plugin.name.replace(/[\"\\\\]/g, \"\\\\$&\");\n    const existing = root.querySelector(\n      `style[data-persona-animation=\"${escaped}\"]`\n    );\n    if (existing) return;\n    names.delete(plugin.name);\n  }\n  names.add(plugin.name);\n  const doc = root instanceof ShadowRoot ? root.ownerDocument : root.ownerDocument ?? document;\n  const style = doc.createElement(\"style\");\n  style.setAttribute(\"data-persona-animation\", plugin.name);\n  style.textContent = plugin.styles;\n  root.appendChild(style);\n};\n\n/**\n * Attach detach-tracking state for plugins registered to a widget root.\n */\nconst attachedCleanups = new WeakMap<\n  HTMLElement | ShadowRoot,\n  Map<string, (() => void) | void>\n>();\n\nexport const attachPlugin = (\n  plugin: StreamAnimationPlugin,\n  root: HTMLElement | ShadowRoot\n): void => {\n  if (!plugin.onAttach) return;\n  let cleanups = attachedCleanups.get(root);\n  if (!cleanups) {\n    cleanups = new Map();\n    attachedCleanups.set(root, cleanups);\n  }\n  if (cleanups.has(plugin.name)) return;\n  const cleanup = plugin.onAttach(root);\n  cleanups.set(plugin.name, cleanup);\n};\n\nexport const detachAllPlugins = (root: HTMLElement | ShadowRoot): void => {\n  const cleanups = attachedCleanups.get(root);\n  if (!cleanups) return;\n  for (const cleanup of cleanups.values()) {\n    if (typeof cleanup === \"function\") cleanup();\n  }\n  cleanups.clear();\n};\n\n/**\n * Ensure the plugin's one-time side effects (style injection, onAttach) have\n * run for this widget root. Idempotent: safe to call on every render.\n */\nexport const ensurePluginActive = (\n  plugin: StreamAnimationPlugin,\n  root: HTMLElement | ShadowRoot\n): void => {\n  injectPluginStyles(plugin, root);\n  attachPlugin(plugin, root);\n};\n\n/* ============================================================\n   Back-compat helpers (used by existing tests)\n   ============================================================ */\n\nexport const isPerCharAnimation = (type: AgentWidgetStreamAnimationType): boolean => {\n  const plugin = resolveStreamAnimationPlugin(type);\n  return plugin?.wrap === \"char\";\n};\n\nexport const isPerWordAnimation = (type: AgentWidgetStreamAnimationType): boolean => {\n  const plugin = resolveStreamAnimationPlugin(type);\n  return plugin?.wrap === \"word\";\n};\n\nexport const isWrappingAnimation = (type: AgentWidgetStreamAnimationType): boolean =>\n  isPerCharAnimation(type) || isPerWordAnimation(type);\n\nexport const streamAnimationContainerClass = (\n  type: AgentWidgetStreamAnimationType\n): string | null => resolveStreamAnimationPlugin(type)?.containerClass ?? null;\n\nexport const streamAnimationBubbleClass = (\n  type: AgentWidgetStreamAnimationType\n): string | null => resolveStreamAnimationPlugin(type)?.bubbleClass ?? null;\n\n// Re-export the builtin type literal so tests and consumers can reference it.\nexport type { AgentWidgetStreamAnimationBuiltinType };\n","import { DEFAULT_OVERLAY_Z_INDEX } from \"./constants\";\n\n/**\n * Elevates the light-DOM host element's stacking context so viewport-covering\n * overlays (sidebar, fullscreen) can escape parent stacking traps.\n *\n * - If the host has `position: static`, sets it to `relative` (required for\n *   `z-index` to take effect).\n * - Applies `z-index` matching the overlay default.\n * - Applies `isolation: isolate` to create a predictable stacking context.\n *\n * @returns A teardown function that restores only the properties that were changed.\n */\nexport function syncOverlayHostStacking(\n  host: HTMLElement,\n  zIndex: number = DEFAULT_OVERLAY_Z_INDEX\n): () => void {\n  const originalPosition = host.style.position;\n  const originalZIndex = host.style.zIndex;\n  const originalIsolation = host.style.isolation;\n\n  const computed = getComputedStyle(host);\n  const positionWasSet = computed.position === \"static\" || computed.position === \"\";\n  if (positionWasSet) {\n    host.style.position = \"relative\";\n  }\n\n  host.style.zIndex = String(zIndex);\n  host.style.isolation = \"isolate\";\n\n  return () => {\n    if (positionWasSet) {\n      host.style.position = originalPosition;\n    }\n    host.style.zIndex = originalZIndex;\n    host.style.isolation = originalIsolation;\n  };\n}\n","interface ScrollLockState {\n  originalOverflow: string;\n  originalPosition: string;\n  originalTop: string;\n  originalWidth: string;\n  scrollY: number;\n}\n\nlet lockCount = 0;\nlet savedState: ScrollLockState | null = null;\n\n/**\n * Acquire a document-level scroll lock. The page body becomes non-scrollable\n * via `overflow: hidden` with an iOS-safe `position: fixed` pattern that\n * preserves the visual scroll position.\n *\n * Ref-counted: multiple callers can acquire; the lock is only released when\n * all callers have released. Each release call is idempotent.\n *\n * @returns A release function. Call it exactly once per acquisition.\n */\nexport function acquireScrollLock(doc: Document = document): () => void {\n  lockCount++;\n\n  if (lockCount === 1) {\n    const body = doc.body;\n    const win = doc.defaultView ?? window;\n    const scrollY = win.scrollY || doc.documentElement.scrollTop;\n\n    savedState = {\n      originalOverflow: body.style.overflow,\n      originalPosition: body.style.position,\n      originalTop: body.style.top,\n      originalWidth: body.style.width,\n      scrollY,\n    };\n\n    body.style.overflow = \"hidden\";\n    body.style.position = \"fixed\";\n    body.style.top = `-${scrollY}px`;\n    body.style.width = \"100%\";\n  }\n\n  let released = false;\n\n  return () => {\n    if (released) return;\n    released = true;\n    lockCount = Math.max(0, lockCount - 1);\n\n    if (lockCount === 0 && savedState) {\n      const body = doc.body;\n      const win = doc.defaultView ?? window;\n      body.style.overflow = savedState.originalOverflow;\n      body.style.position = savedState.originalPosition;\n      body.style.top = savedState.originalTop;\n      body.style.width = savedState.originalWidth;\n      win.scrollTo(0, savedState.scrollY);\n      savedState = null;\n    }\n  };\n}\n","import type { AgentWidgetConfig, AgentWidgetDockConfig } from \"../types\";\n\nconst DEFAULT_DOCK_CONFIG: Required<AgentWidgetDockConfig> = {\n  side: \"right\",\n  width: \"420px\",\n  animate: true,\n  reveal: \"resize\",\n  maxHeight: \"100dvh\",\n};\n\nexport const isDockedMountMode = (config?: AgentWidgetConfig): boolean =>\n  (config?.launcher?.mountMode ?? \"floating\") === \"docked\";\n\nexport const isComposerBarMountMode = (config?: AgentWidgetConfig): boolean =>\n  (config?.launcher?.mountMode ?? \"floating\") === \"composer-bar\";\n\n/**\n * Resolved dock layout. For `reveal: \"resize\"`, when the panel is closed the dock column is `0px`.\n * For `reveal: \"overlay\"`, the panel overlays with `transform`. For `reveal: \"push\"`, a sliding track\n * moves content and panel together without width animation on the main column. For `emerge`,\n * the dock column still animates like `resize` but the widget stays `dock.width` wide inside the slot.\n * Unknown keys on `launcher.dock` (e.g. legacy `collapsedWidth`) are ignored.\n */\nexport const resolveDockConfig = (\n  config?: AgentWidgetConfig\n): Required<AgentWidgetDockConfig> => {\n  const dock = config?.launcher?.dock;\n  return {\n    side: dock?.side ?? DEFAULT_DOCK_CONFIG.side,\n    width: dock?.width ?? DEFAULT_DOCK_CONFIG.width,\n    animate: dock?.animate ?? DEFAULT_DOCK_CONFIG.animate,\n    reveal: dock?.reveal ?? DEFAULT_DOCK_CONFIG.reveal,\n    maxHeight: dock?.maxHeight ?? DEFAULT_DOCK_CONFIG.maxHeight,\n  };\n};\n","export const positionMap: Record<\n  \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\",\n  string\n> = {\n  \"bottom-right\": \"persona-bottom-6 persona-right-6\",\n  \"bottom-left\": \"persona-bottom-6 persona-left-6\",\n  \"top-right\": \"persona-top-6 persona-right-6\",\n  \"top-left\": \"persona-top-6 persona-left-6\"\n};\n\n\n\n\n\n\n\n\n","import { createElement, createElementInDocument, createNode, cx } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { AgentWidgetConfig } from \"../types\";\nimport { PORTALED_OVERLAY_Z_INDEX } from \"../utils/constants\";\nimport { HEADER_THEME_CSS } from \"./header-builder\";\n\nexport interface CloseButtonParts {\n  button: HTMLButtonElement;\n  wrapper: HTMLElement;\n}\n\nexport interface ClearChatButtonParts {\n  button: HTMLButtonElement;\n  wrapper: HTMLElement;\n}\n\nexport interface CreateCloseButtonOptions {\n  showClose?: boolean;\n  /**\n   * Override the wrapper className. The full header passes its own\n   * placement-aware class string; composer-bar mode passes a class that\n   * positions the wrapper absolutely in the top-right of the panel chrome.\n   */\n  wrapperClassName?: string;\n  /**\n   * Explicit button-size override that wins over `launcher.closeButtonSize`.\n   * Use when the call site has its own opinion about the size that should\n   * take precedence over the global launcher config: e.g. composer-bar's\n   * minimal close icon, where size is part of the mode's UX, not something\n   * that should inherit from the floating launcher's button size.\n   */\n  buttonSize?: string;\n  /**\n   * Override the rendered icon size (default: \"28px\"). Pair with\n   * `buttonSize` when scaling the whole control down: otherwise the\n   * 28px icon will overflow a smaller button.\n   */\n  iconSize?: string;\n}\n\nexport interface CreateClearChatButtonOptions {\n  /**\n   * Override the wrapper className. Header builder passes its own\n   * placement-aware class string; composer-bar mode passes a class that\n   * positions the wrapper absolutely (next to the close button).\n   */\n  wrapperClassName?: string;\n  /**\n   * Explicit button-size override that wins over `launcher.clearChat.size`.\n   * Composer-bar mode uses this so the clear icon visually matches the\n   * shrunken close button (16px) and doesn't render at the floating\n   * launcher's 32px default.\n   */\n  buttonSize?: string;\n  /** Override the rendered icon size (default: \"20px\"). */\n  iconSize?: string;\n}\n\nconst DEFAULT_WRAPPER_CLASS =\n  \"persona-relative persona-ml-auto persona-inline-flex persona-items-center persona-justify-center\";\n\n/**\n * Build the close (×) button + tooltip used in the panel header. Lifted\n * verbatim from header-builder.ts so composer-bar mode can render just a\n * close button (no full header strip) without duplicating the tooltip\n * + config-driven styling logic.\n */\nexport const createCloseButton = (\n  config: AgentWidgetConfig | undefined,\n  options: CreateCloseButtonOptions = {},\n): CloseButtonParts => {\n  const {\n    showClose = true,\n    wrapperClassName = DEFAULT_WRAPPER_CLASS,\n    buttonSize,\n    iconSize = \"28px\",\n  } = options;\n  const launcher = config?.launcher ?? {};\n  // Call-site `buttonSize` (if provided) wins over launcher config. The\n  // launcher's `closeButtonSize` is set in DEFAULT_WIDGET_CONFIG so it's\n  // never undefined, which means the call-site override is the only way\n  // to opt a specific render path (like composer-bar's minimal close) into\n  // a different size.\n  const closeButtonSize = buttonSize ?? launcher.closeButtonSize ?? \"32px\";\n\n  const wrapper = createElement(\"div\", wrapperClassName);\n\n  const closeButtonTooltipText = launcher.closeButtonTooltipText ?? \"Close chat\";\n  const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;\n  const closeButtonIconName = launcher.closeButtonIconName ?? \"x\";\n  const closeButtonIconText = launcher.closeButtonIconText ?? \"×\";\n  // hover-bg / border-none / rounded-full are default utility classes that\n  // apply only when the matching style override is absent; an override sets\n  // the inline style instead (cx omits the class).\n  const hasCloseBorder = Boolean(\n    launcher.closeButtonBorderWidth || launcher.closeButtonBorderColor\n  );\n\n  const button = createNode(\"button\", {\n    className: cx(\n      \"persona-inline-flex persona-items-center persona-justify-center persona-cursor-pointer\",\n      !launcher.closeButtonBackgroundColor && \"hover:persona-bg-gray-100\",\n      !hasCloseBorder && \"persona-border-none\",\n      !launcher.closeButtonBorderRadius && \"persona-rounded-full\"\n    ),\n    attrs: { type: \"button\", \"aria-label\": closeButtonTooltipText },\n    style: {\n      height: closeButtonSize,\n      width: closeButtonSize,\n      display: showClose ? undefined : \"none\",\n      color: launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor,\n      backgroundColor: launcher.closeButtonBackgroundColor || undefined,\n      border: hasCloseBorder\n        ? `${launcher.closeButtonBorderWidth || \"0px\"} solid ${launcher.closeButtonBorderColor || \"transparent\"}`\n        : undefined,\n      borderRadius: launcher.closeButtonBorderRadius || undefined,\n      paddingLeft: launcher.closeButtonPaddingX || undefined,\n      paddingRight: launcher.closeButtonPaddingX || undefined,\n      paddingTop: launcher.closeButtonPaddingY || undefined,\n      paddingBottom: launcher.closeButtonPaddingY || undefined,\n    },\n  });\n\n  // The X glyph's paths occupy only the middle 50% of its 24x24 viewBox\n  // (from 6,6 to 18,18), while other header icons (e.g. refresh-cw) span\n  // ~75% of the viewBox. Rendering X at a larger intrinsic size brings\n  // its visible extent into parity with sibling icons in the header.\n  // display:block eliminates inline-baseline spacing that can push the\n  // icon a fractional pixel off-center inside the button.\n  const closeIconSvg = renderLucideIcon(closeButtonIconName, iconSize, \"currentColor\", 1);\n  if (closeIconSvg) {\n    closeIconSvg.style.display = \"block\";\n    button.appendChild(closeIconSvg);\n  } else {\n    button.textContent = closeButtonIconText;\n  }\n\n  wrapper.appendChild(button);\n\n  if (closeButtonShowTooltip && closeButtonTooltipText) {\n    let portaledTooltip: HTMLElement | null = null;\n\n    const showTooltip = () => {\n      if (portaledTooltip) return;\n\n      const tooltipDocument = button.ownerDocument;\n      const tooltipContainer = tooltipDocument.body;\n      if (!tooltipContainer) return;\n\n      portaledTooltip = createElementInDocument(\n        tooltipDocument,\n        \"div\",\n        \"persona-clear-chat-tooltip\"\n      );\n      portaledTooltip.textContent = closeButtonTooltipText;\n\n      const arrow = createElementInDocument(tooltipDocument, \"div\");\n      arrow.className = \"persona-clear-chat-tooltip-arrow\";\n      portaledTooltip.appendChild(arrow);\n\n      const buttonRect = button.getBoundingClientRect();\n\n      portaledTooltip.style.position = \"fixed\";\n      portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);\n      portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;\n      portaledTooltip.style.top = `${buttonRect.top - 8}px`;\n      portaledTooltip.style.transform = \"translate(-50%, -100%)\";\n\n      tooltipContainer.appendChild(portaledTooltip);\n    };\n\n    const hideTooltip = () => {\n      if (portaledTooltip && portaledTooltip.parentNode) {\n        portaledTooltip.parentNode.removeChild(portaledTooltip);\n        portaledTooltip = null;\n      }\n    };\n\n    wrapper.addEventListener(\"mouseenter\", showTooltip);\n    wrapper.addEventListener(\"mouseleave\", hideTooltip);\n    button.addEventListener(\"focus\", showTooltip);\n    button.addEventListener(\"blur\", hideTooltip);\n\n    (wrapper as any)._cleanupTooltip = () => {\n      hideTooltip();\n      wrapper.removeEventListener(\"mouseenter\", showTooltip);\n      wrapper.removeEventListener(\"mouseleave\", hideTooltip);\n      button.removeEventListener(\"focus\", showTooltip);\n      button.removeEventListener(\"blur\", hideTooltip);\n    };\n  }\n\n  return { button, wrapper };\n};\n\nconst DEFAULT_CLEAR_CHAT_WRAPPER_CLASS =\n  \"persona-relative persona-ml-auto persona-clear-chat-button-wrapper\";\n\n/**\n * Build the clear-chat (refresh) button + tooltip used in the panel header.\n * Extracted from `header-builder.ts` so composer-bar mode can render a\n * \"start over\" button alongside its close icon without duplicating the\n * tooltip + config-driven styling logic.\n *\n * The factory only handles construction. Wiring the click to the\n * clear-history handler is owned by `setupClearChatButton()` in `ui.ts`,\n * which keys off `panelElements.clearChatButton`.\n */\nexport const createClearChatButton = (\n  config: AgentWidgetConfig | undefined,\n  options: CreateClearChatButtonOptions = {},\n): ClearChatButtonParts => {\n  const {\n    wrapperClassName = DEFAULT_CLEAR_CHAT_WRAPPER_CLASS,\n    buttonSize,\n    iconSize = \"20px\",\n  } = options;\n\n  const launcher = config?.launcher ?? {};\n  const clearChatConfig = launcher.clearChat ?? {};\n  // Call-site `buttonSize` (when provided) wins over launcher.clearChat.size.\n  // Same precedence rule as createCloseButton: callers like composer-bar\n  // intentionally override the inherited launcher default to fit their UX.\n  const clearChatSize = buttonSize ?? clearChatConfig.size ?? \"32px\";\n  const clearChatIconName = clearChatConfig.iconName ?? \"refresh-cw\";\n  const clearChatIconColor = clearChatConfig.iconColor ?? \"\";\n  const clearChatBgColor = clearChatConfig.backgroundColor ?? \"\";\n  const clearChatBorderWidth = clearChatConfig.borderWidth ?? \"\";\n  const clearChatBorderColor = clearChatConfig.borderColor ?? \"\";\n  const clearChatBorderRadius = clearChatConfig.borderRadius ?? \"\";\n  const clearChatPaddingX = clearChatConfig.paddingX ?? \"\";\n  const clearChatPaddingY = clearChatConfig.paddingY ?? \"\";\n  const clearChatTooltipText = clearChatConfig.tooltipText ?? \"Clear chat\";\n  const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;\n\n  const wrapper = createElement(\"div\", wrapperClassName);\n\n  // hover-bg / border-none / rounded-full are default utility classes that\n  // apply only when the matching style override is absent; an override sets\n  // the inline style instead (cx omits the class).\n  const hasClearChatBorder = Boolean(clearChatBorderWidth || clearChatBorderColor);\n\n  const button = createNode(\"button\", {\n    className: cx(\n      \"persona-inline-flex persona-items-center persona-justify-center persona-cursor-pointer\",\n      !clearChatBgColor && \"hover:persona-bg-gray-100\",\n      !hasClearChatBorder && \"persona-border-none\",\n      !clearChatBorderRadius && \"persona-rounded-full\"\n    ),\n    attrs: { type: \"button\", \"aria-label\": clearChatTooltipText },\n    style: {\n      height: clearChatSize,\n      width: clearChatSize,\n      color: clearChatIconColor || HEADER_THEME_CSS.actionIconColor,\n      backgroundColor: clearChatBgColor || undefined,\n      border: hasClearChatBorder\n        ? `${clearChatBorderWidth || \"0px\"} solid ${clearChatBorderColor || \"transparent\"}`\n        : undefined,\n      borderRadius: clearChatBorderRadius || undefined,\n      paddingLeft: clearChatPaddingX || undefined,\n      paddingRight: clearChatPaddingX || undefined,\n      paddingTop: clearChatPaddingY || undefined,\n      paddingBottom: clearChatPaddingY || undefined,\n    },\n  });\n\n  const iconSvg = renderLucideIcon(clearChatIconName, iconSize, \"currentColor\", 1);\n  if (iconSvg) {\n    iconSvg.style.display = \"block\";\n    button.appendChild(iconSvg);\n  }\n\n  wrapper.appendChild(button);\n\n  if (clearChatShowTooltip && clearChatTooltipText) {\n    let portaledTooltip: HTMLElement | null = null;\n\n    const showTooltip = () => {\n      if (portaledTooltip) return;\n\n      const tooltipDocument = button.ownerDocument;\n      const tooltipContainer = tooltipDocument.body;\n      if (!tooltipContainer) return;\n\n      portaledTooltip = createElementInDocument(\n        tooltipDocument,\n        \"div\",\n        \"persona-clear-chat-tooltip\"\n      );\n      portaledTooltip.textContent = clearChatTooltipText;\n\n      const arrow = createElementInDocument(tooltipDocument, \"div\");\n      arrow.className = \"persona-clear-chat-tooltip-arrow\";\n      portaledTooltip.appendChild(arrow);\n\n      const buttonRect = button.getBoundingClientRect();\n\n      portaledTooltip.style.position = \"fixed\";\n      portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);\n      portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;\n      portaledTooltip.style.top = `${buttonRect.top - 8}px`;\n      portaledTooltip.style.transform = \"translate(-50%, -100%)\";\n\n      tooltipContainer.appendChild(portaledTooltip);\n    };\n\n    const hideTooltip = () => {\n      if (portaledTooltip && portaledTooltip.parentNode) {\n        portaledTooltip.parentNode.removeChild(portaledTooltip);\n        portaledTooltip = null;\n      }\n    };\n\n    wrapper.addEventListener(\"mouseenter\", showTooltip);\n    wrapper.addEventListener(\"mouseleave\", hideTooltip);\n    button.addEventListener(\"focus\", showTooltip);\n    button.addEventListener(\"blur\", hideTooltip);\n\n    (wrapper as any)._cleanupTooltip = () => {\n      hideTooltip();\n      wrapper.removeEventListener(\"mouseenter\", showTooltip);\n      wrapper.removeEventListener(\"mouseleave\", hideTooltip);\n      button.removeEventListener(\"focus\", showTooltip);\n      button.removeEventListener(\"blur\", hideTooltip);\n    };\n  }\n\n  return { button, wrapper };\n};\n","import { createElement, createNode } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { AgentWidgetConfig } from \"../types\";\nimport { createCloseButton, createClearChatButton } from \"./header-parts\";\n\n/** CSS `color` values; variables are set on `[data-persona-root]` from `theme.components.header`. */\nexport const HEADER_THEME_CSS = {\n  titleColor:\n    \"var(--persona-header-title-fg, var(--persona-primary, #0f0f0f))\",\n  subtitleColor:\n    \"var(--persona-header-subtitle-fg, var(--persona-text-muted, var(--persona-muted, #9ca3af)))\",\n  actionIconColor:\n    \"var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af))\",\n} as const;\n\nexport interface HeaderElements {\n  header: HTMLElement;\n  iconHolder: HTMLElement;\n  headerTitle: HTMLElement;\n  headerSubtitle: HTMLElement;\n  closeButton: HTMLButtonElement;\n  closeButtonWrapper: HTMLElement;\n  clearChatButton: HTMLButtonElement | null;\n  clearChatButtonWrapper: HTMLElement | null;\n}\n\nexport interface HeaderBuildContext {\n  config?: AgentWidgetConfig;\n  showClose?: boolean;\n  onClose?: () => void;\n  onClearChat?: () => void;\n}\n\n/**\n * Build the header section of the panel.\n * Extracted for reuse and plugin override support.\n */\nexport const buildHeader = (context: HeaderBuildContext): HeaderElements => {\n  const { config, showClose = true } = context;\n\n  const header = createNode(\"div\", {\n    className:\n      \"persona-widget-header persona-flex persona-items-center persona-gap-3 persona-px-6 persona-py-5\",\n    attrs: { \"data-persona-theme-zone\": \"header\" },\n    style: {\n      backgroundColor: \"var(--persona-header-bg, var(--persona-surface, #ffffff))\",\n      borderBottomColor: \"var(--persona-header-border, var(--persona-divider, #f1f5f9))\",\n      boxShadow: \"var(--persona-header-shadow, none)\",\n      borderBottom:\n        \"var(--persona-header-border-bottom, 1px solid var(--persona-header-border, var(--persona-divider, #f1f5f9)))\",\n    },\n  });\n\n  const launcher = config?.launcher ?? {};\n  const headerIconSize = launcher.headerIconSize ?? \"48px\";\n  const closeButtonPlacement = launcher.closeButtonPlacement ?? \"inline\";\n  const headerIconHidden = launcher.headerIconHidden ?? false;\n  const headerIconName = launcher.headerIconName;\n\n  const iconHolder = createNode(\"div\", {\n    className:\n      \"persona-flex persona-items-center persona-justify-center persona-rounded-xl persona-text-xl\",\n    style: {\n      height: headerIconSize,\n      width: headerIconSize,\n      backgroundColor: \"var(--persona-header-icon-bg, var(--persona-primary, #0f0f0f))\",\n      color: \"var(--persona-header-icon-fg, var(--persona-text-inverse, #ffffff))\",\n    },\n  });\n\n  // Render icon based on priority: Lucide icon > iconUrl > agentIconText\n  if (!headerIconHidden) {\n    if (headerIconName) {\n      // Use Lucide icon\n      const iconSize = parseFloat(headerIconSize) || 24;\n      const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, \"currentColor\", 1);\n      if (iconSvg) {\n        iconHolder.replaceChildren(iconSvg);\n      } else {\n        // Fallback to agentIconText if Lucide icon fails\n        iconHolder.textContent = config?.launcher?.agentIconText ?? \"💬\";\n      }\n    } else if (config?.launcher?.iconUrl) {\n      // Use image URL\n      const img = createElement(\"img\") as HTMLImageElement;\n      img.src = config.launcher.iconUrl;\n      img.alt = \"\";\n      img.className = \"persona-rounded-xl persona-object-cover\";\n      img.style.height = headerIconSize;\n      img.style.width = headerIconSize;\n      iconHolder.replaceChildren(img);\n    } else {\n      // Use text/emoji\n      iconHolder.textContent = config?.launcher?.agentIconText ?? \"💬\";\n    }\n  }\n\n  const headerCopy = createElement(\"div\", \"persona-flex persona-flex-col persona-flex-1 persona-min-w-0\");\n  const title = createNode(\"span\", {\n    className: \"persona-text-base persona-font-semibold\",\n    text: config?.launcher?.title ?? \"Chat Assistant\",\n    style: { color: HEADER_THEME_CSS.titleColor },\n  });\n  const subtitle = createNode(\"span\", {\n    className: \"persona-text-xs\",\n    text: config?.launcher?.subtitle ?? \"Here to help you get answers fast\",\n    style: { color: HEADER_THEME_CSS.subtitleColor },\n  });\n\n  headerCopy.append(title, subtitle);\n\n  // Only append iconHolder if not hidden\n  if (!headerIconHidden) {\n    header.append(iconHolder, headerCopy);\n  } else {\n    header.append(headerCopy);\n  }\n\n  // Create clear chat button if enabled\n  const clearChatConfig = launcher.clearChat ?? {};\n  const clearChatEnabled = clearChatConfig.enabled ?? true;\n  const clearChatPlacement = clearChatConfig.placement ?? \"inline\";\n  let clearChatButton: HTMLButtonElement | null = null;\n  let clearChatButtonWrapper: HTMLElement | null = null;\n\n  if (clearChatEnabled) {\n    // Top-right placement uses an absolute wrapper offset from the close\n    // button (which lives at right: 16px and is ~32px wide, leaving ~48px\n    // from the panel's right edge for the clear icon).\n    const wrapperClassName =\n      clearChatPlacement === \"top-right\"\n        ? \"persona-absolute persona-top-4 persona-z-50\"\n        : \"persona-relative persona-ml-auto persona-clear-chat-button-wrapper\";\n\n    const parts = createClearChatButton(config, { wrapperClassName });\n    clearChatButton = parts.button;\n    clearChatButtonWrapper = parts.wrapper;\n\n    if (clearChatPlacement === \"top-right\") {\n      clearChatButtonWrapper.style.right = \"48px\";\n    }\n\n    // Only append to header if inline placement\n    if (clearChatPlacement === \"inline\") {\n      header.appendChild(clearChatButtonWrapper);\n    }\n  }\n\n  // Build the close (×) button via the shared factory. The wrapper class\n  // mirrors the clear-chat wrapper's inline-flex centering so both header\n  // action buttons vertically align identically within the header's flex\n  // row. composer-bar mode uses the same factory directly with its own\n  // wrapper class to render a top-right-only close button.\n  const closeButtonWrapperClass =\n    closeButtonPlacement === \"top-right\"\n      ? \"persona-absolute persona-top-4 persona-right-4 persona-z-50\"\n      : clearChatEnabled && clearChatPlacement === \"inline\"\n        ? \"persona-relative persona-inline-flex persona-items-center persona-justify-center\"\n        : \"persona-relative persona-ml-auto persona-inline-flex persona-items-center persona-justify-center\";\n\n  const { button: closeButton, wrapper: closeButtonWrapper } = createCloseButton(\n    config,\n    { showClose, wrapperClassName: closeButtonWrapperClass }\n  );\n\n  // Inline placement: append close button to header\n  if (closeButtonPlacement !== \"top-right\") {\n    header.appendChild(closeButtonWrapper);\n  }\n\n  return {\n    header,\n    iconHolder,\n    headerTitle: title,\n    headerSubtitle: subtitle,\n    closeButton,\n    closeButtonWrapper,\n    clearChatButton,\n    clearChatButtonWrapper\n  };\n};\n\n/**\n * Attach header elements to the container, handling placement modes.\n */\nexport const attachHeaderToContainer = (\n  container: HTMLElement,\n  headerElements: HeaderElements,\n  config?: AgentWidgetConfig\n): void => {\n  const launcher = config?.launcher ?? {};\n  const closeButtonPlacement = launcher.closeButtonPlacement ?? \"inline\";\n  const clearChatPlacement = launcher.clearChat?.placement ?? \"inline\";\n\n  // Add header to container\n  container.appendChild(headerElements.header);\n\n  // Position close button wrapper if top-right placement\n  if (closeButtonPlacement === \"top-right\") {\n    container.style.position = \"relative\";\n    container.appendChild(headerElements.closeButtonWrapper);\n  }\n\n  // Position clear chat button wrapper if top-right placement\n  if (\n    headerElements.clearChatButtonWrapper &&\n    clearChatPlacement === \"top-right\"\n  ) {\n    container.style.position = \"relative\";\n    container.appendChild(headerElements.clearChatButtonWrapper);\n  }\n};\n\n","import { createElement } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { createDropdownMenu } from \"../utils/dropdown\";\nimport { createComboButton } from \"../utils/buttons\";\nimport {\n  AgentWidgetConfig,\n  AgentWidgetHeaderLayoutConfig,\n  AgentWidgetHeaderTrailingAction\n} from \"../types\";\nimport {\n  buildHeader,\n  HEADER_THEME_CSS,\n  HeaderElements,\n  attachHeaderToContainer as _attachHeaderToContainer,\n} from \"./header-builder\";\n\nexport interface HeaderLayoutContext {\n  config: AgentWidgetConfig;\n  showClose?: boolean;\n  onClose?: () => void;\n  onClearChat?: () => void;\n  /** Passed from `buildHeaderWithLayout` for minimal/default chrome extensions */\n  layoutHeaderConfig?: AgentWidgetHeaderLayoutConfig;\n  onHeaderAction?: (actionId: string) => void;\n}\n\nexport type HeaderLayoutRenderer = (context: HeaderLayoutContext) => HeaderElements;\n\n/**\n * Build default header layout\n * Full header with icon, title, subtitle, clear chat, and close button\n */\nexport const buildDefaultHeader: HeaderLayoutRenderer = (context) => {\n  const elements = buildHeader({\n    config: context.config,\n    showClose: context.showClose,\n    onClose: context.onClose,\n    onClearChat: context.onClearChat\n  });\n\n  // Make the title/subtitle area clickable when onTitleClick is provided\n  const onTitleClick = context.layoutHeaderConfig?.onTitleClick;\n  if (onTitleClick) {\n    const headerCopy = elements.headerTitle.parentElement;\n    if (headerCopy) {\n      headerCopy.style.cursor = \"pointer\";\n      headerCopy.setAttribute(\"role\", \"button\");\n      headerCopy.setAttribute(\"tabindex\", \"0\");\n      headerCopy.addEventListener(\"click\", () => onTitleClick());\n      headerCopy.addEventListener(\"keydown\", (e) => {\n        if (e.key === \"Enter\" || e.key === \" \") {\n          e.preventDefault();\n          onTitleClick();\n        }\n      });\n    }\n  }\n\n  return elements;\n};\n\n/**\n * Build minimal header layout\n * Simplified layout with just title and close button\n */\nfunction appendTrailingHeaderActions(\n  container: HTMLElement,\n  actions: AgentWidgetHeaderTrailingAction[] | undefined,\n  onAction?: (id: string) => void\n): void {\n  if (!actions?.length) return;\n  for (const a of actions) {\n    const btn = createElement(\n      \"button\",\n      \"persona-inline-flex persona-items-center persona-justify-center persona-rounded-md persona-border-none persona-bg-transparent persona-p-0 persona-text-persona-muted hover:persona-opacity-80\"\n    ) as HTMLButtonElement;\n    btn.type = \"button\";\n    btn.setAttribute(\"aria-label\", a.ariaLabel ?? a.label ?? a.id);\n    if (a.icon) {\n      const ic = renderLucideIcon(a.icon, 14, \"currentColor\", 2);\n      if (ic) btn.appendChild(ic);\n    } else if (a.label) {\n      btn.textContent = a.label;\n    }\n\n    if (a.menuItems?.length) {\n      // Wrap in a relative container for dropdown positioning\n      const wrapper = createElement(\"div\", \"persona-relative\");\n      wrapper.appendChild(btn);\n      const dropdown = createDropdownMenu({\n        items: a.menuItems,\n        onSelect: (itemId) => onAction?.(itemId),\n        anchor: wrapper,\n        position: 'bottom-left',\n      });\n      wrapper.appendChild(dropdown.element);\n      btn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        dropdown.toggle();\n      });\n      container.appendChild(wrapper);\n    } else {\n      btn.addEventListener(\"click\", () => onAction?.(a.id));\n      container.appendChild(btn);\n    }\n  }\n}\n\nexport const buildMinimalHeader: HeaderLayoutRenderer = (context) => {\n  const { config, showClose = true, onClose, layoutHeaderConfig, onHeaderAction } = context;\n  const launcher = config?.launcher ?? {};\n\n  const header = createElement(\n    \"div\",\n    \"persona-flex persona-items-center persona-justify-between persona-px-6 persona-py-4\"\n  );\n  header.setAttribute(\"data-persona-theme-zone\", \"header\");\n  header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';\n  header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';\n  header.style.boxShadow = 'var(--persona-header-shadow, none)';\n  header.style.borderBottom =\n    'var(--persona-header-border-bottom, 1px solid var(--persona-header-border, var(--persona-divider, #f1f5f9)))';\n\n  // Build the title area: either a combo button (titleMenu) or standard title row\n  const titleMenuConfig = layoutHeaderConfig?.titleMenu;\n  let titleRow: HTMLElement;\n  let headerTitle: HTMLElement;\n\n  if (titleMenuConfig) {\n    // Combo button replaces title + trailing actions + hover\n    const combo = createComboButton({\n      label: launcher.title ?? \"Chat Assistant\",\n      menuItems: titleMenuConfig.menuItems,\n      onSelect: titleMenuConfig.onSelect,\n      hover: titleMenuConfig.hover,\n      className: \"\",\n    });\n    titleRow = combo.element;\n    titleRow.style.color = HEADER_THEME_CSS.titleColor;\n    // The combo button's label span acts as headerTitle for update()\n    headerTitle = titleRow.querySelector(\".persona-combo-btn-label\") ?? titleRow;\n  } else {\n    titleRow = createElement(\n      \"div\",\n      \"persona-flex persona-min-w-0 persona-flex-1 persona-items-center persona-gap-1\"\n    );\n\n    // Title only (no icon, no subtitle)\n    headerTitle = createElement(\"span\", \"persona-text-base persona-font-semibold persona-truncate\");\n    headerTitle.style.color = HEADER_THEME_CSS.titleColor;\n    headerTitle.textContent = launcher.title ?? \"Chat Assistant\";\n\n    titleRow.appendChild(headerTitle);\n    appendTrailingHeaderActions(\n      titleRow,\n      layoutHeaderConfig?.trailingActions,\n      layoutHeaderConfig?.onAction ?? onHeaderAction\n    );\n\n    // Make title row clickable when onTitleClick is provided\n    if (layoutHeaderConfig?.onTitleClick) {\n      titleRow.style.cursor = \"pointer\";\n      titleRow.setAttribute(\"role\", \"button\");\n      titleRow.setAttribute(\"tabindex\", \"0\");\n      const handleTitleClick = layoutHeaderConfig.onTitleClick;\n      titleRow.addEventListener(\"click\", (e) => {\n        if ((e.target as HTMLElement).closest(\"button\")) return;\n        handleTitleClick();\n      });\n      titleRow.addEventListener(\"keydown\", (e) => {\n        if (e.key === \"Enter\" || e.key === \" \") {\n          e.preventDefault();\n          handleTitleClick();\n        }\n      });\n    }\n\n    // Title row hover pill effect\n    const hoverCfg = layoutHeaderConfig?.titleRowHover;\n    if (hoverCfg) {\n      titleRow.style.borderRadius = hoverCfg.borderRadius ?? '10px';\n      titleRow.style.padding = hoverCfg.padding ?? '6px 4px 6px 12px';\n      titleRow.style.margin = '-6px 0 -6px -12px';\n      titleRow.style.border = '1px solid transparent';\n      titleRow.style.transition = 'background-color 0.15s ease, border-color 0.15s ease';\n      titleRow.style.width = 'fit-content';\n      titleRow.style.flex = 'none';\n      titleRow.addEventListener('mouseenter', () => {\n        titleRow.style.backgroundColor = hoverCfg.background ?? '';\n        titleRow.style.borderColor = hoverCfg.border ?? '';\n      });\n      titleRow.addEventListener('mouseleave', () => {\n        titleRow.style.backgroundColor = '';\n        titleRow.style.borderColor = 'transparent';\n      });\n    }\n  }\n\n  header.appendChild(titleRow);\n\n  // Close button\n  const closeButtonSize = launcher.closeButtonSize ?? \"32px\";\n  const closeButtonWrapper = createElement(\"div\", \"\");\n\n  const closeButton = createElement(\n    \"button\",\n    \"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none\"\n  ) as HTMLButtonElement;\n  closeButton.style.height = closeButtonSize;\n  closeButton.style.width = closeButtonSize;\n  closeButton.type = \"button\";\n  closeButton.setAttribute(\"aria-label\", \"Close chat\");\n  closeButton.style.display = showClose ? \"\" : \"none\";\n  closeButton.style.color =\n    launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;\n\n  const closeButtonIconName = launcher.closeButtonIconName ?? \"x\";\n  // Larger intrinsic size compensates for the X glyph's sparse viewBox\n  // (paths only occupy the middle 50%). Matches header-builder.ts.\n  const closeIconSvg = renderLucideIcon(closeButtonIconName, \"28px\", \"currentColor\", 1);\n  if (closeIconSvg) {\n    closeButton.appendChild(closeIconSvg);\n  } else {\n    closeButton.textContent = \"×\";\n  }\n\n  if (onClose) {\n    closeButton.addEventListener(\"click\", onClose);\n  }\n\n  closeButtonWrapper.appendChild(closeButton);\n  header.appendChild(closeButtonWrapper);\n\n  // title was moved into titleRow; keep headerTitle ref pointing at title for updateController\n\n  // Create placeholder elements for compatibility\n  const iconHolder = createElement(\"div\");\n  iconHolder.style.display = \"none\";\n  const headerSubtitle = createElement(\"span\");\n  headerSubtitle.style.display = \"none\";\n\n  return {\n    header,\n    iconHolder,\n    headerTitle,\n    headerSubtitle,\n    closeButton,\n    closeButtonWrapper,\n    clearChatButton: null,\n    clearChatButtonWrapper: null\n  };\n};\n\n/**\n * Header layout registry\n * Maps layout names to their renderer functions\n */\nexport const headerLayouts: Record<string, HeaderLayoutRenderer> = {\n  default: buildDefaultHeader,\n  minimal: buildMinimalHeader\n};\n\n/**\n * Get header layout renderer by name\n */\nexport const getHeaderLayout = (layoutName: string): HeaderLayoutRenderer => {\n  return headerLayouts[layoutName] ?? headerLayouts.default;\n};\n\n/**\n * Build header based on layout configuration\n * Applies layout config settings to determine which layout to use\n */\nexport const buildHeaderWithLayout = (\n  config: AgentWidgetConfig,\n  layoutConfig?: AgentWidgetHeaderLayoutConfig,\n  context?: Partial<HeaderLayoutContext>\n): HeaderElements => {\n  // If custom render is provided, use it\n  if (layoutConfig?.render) {\n    const customHeader = layoutConfig.render({\n      config,\n      onClose: context?.onClose,\n      onClearChat: context?.onClearChat,\n      trailingActions: layoutConfig.trailingActions,\n      onAction: layoutConfig.onAction\n    });\n    \n    // Wrap in HeaderElements structure\n    const iconHolder = createElement(\"div\");\n    iconHolder.style.display = \"none\";\n    const headerTitle = createElement(\"span\");\n    const headerSubtitle = createElement(\"span\");\n    const closeButton = createElement(\"button\") as HTMLButtonElement;\n    closeButton.style.display = \"none\";\n    const closeButtonWrapper = createElement(\"div\");\n    closeButtonWrapper.style.display = \"none\";\n    \n    return {\n      header: customHeader,\n      iconHolder,\n      headerTitle,\n      headerSubtitle,\n      closeButton,\n      closeButtonWrapper,\n      clearChatButton: null,\n      clearChatButtonWrapper: null\n    };\n  }\n\n  // Get layout renderer\n  const layoutName = layoutConfig?.layout ?? \"default\";\n  const layoutRenderer = getHeaderLayout(layoutName);\n\n  // Build header with layout\n  const headerElements = layoutRenderer({\n    config,\n    showClose: layoutConfig?.showCloseButton ?? context?.showClose ?? true,\n    onClose: context?.onClose,\n    onClearChat: context?.onClearChat,\n    layoutHeaderConfig: layoutConfig,\n    onHeaderAction: layoutConfig?.onAction\n  });\n\n  // Apply visibility settings from layout config\n  if (layoutConfig) {\n    if (layoutConfig.showIcon === false) {\n      headerElements.iconHolder.style.display = \"none\";\n    }\n    if (layoutConfig.showTitle === false) {\n      headerElements.headerTitle.style.display = \"none\";\n    }\n    if (layoutConfig.showSubtitle === false) {\n      headerElements.headerSubtitle.style.display = \"none\";\n    }\n    if (layoutConfig.showCloseButton === false) {\n      headerElements.closeButton.style.display = \"none\";\n    }\n    if (layoutConfig.showClearChat === false && headerElements.clearChatButtonWrapper) {\n      headerElements.clearChatButtonWrapper.style.display = \"none\";\n    }\n  }\n\n  return headerElements;\n};\n\n","import { createElement, createNode, cx } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { AgentWidgetConfig } from \"../types\";\nimport { ALL_SUPPORTED_MIME_TYPES } from \"../utils/content\";\n\n/**\n * Low-level composer control factories. Both `buildComposer` (full,\n * column-stacked card) and `buildPillComposer` (single-row pill) consume\n * these: the only meaningful difference between the two composers is the\n * layout shell + className. No DOM assembly here; each factory returns the\n * element plus any handles the caller needs.\n *\n * Stable selectors (data attributes + class hooks) live with the elements\n * so `bindComposerRefsFromFooter()` in ui.ts finds them regardless of\n * which builder ran.\n */\n\nexport interface ComposerTextareaParts {\n  textarea: HTMLTextAreaElement;\n  /**\n   * Wire the input listener that grows the textarea up to its current\n   * `maxHeight`. Caller decides when to attach (full composer attaches\n   * immediately; pill composer also attaches because expanded mode users\n   * want multi-line composition).\n   */\n  attachAutoResize: () => void;\n}\n\nexport const createComposerTextarea = (config?: AgentWidgetConfig): ComposerTextareaParts => {\n  const textarea = createElement(\"textarea\") as HTMLTextAreaElement;\n  textarea.setAttribute(\"data-persona-composer-input\", \"\");\n  textarea.placeholder = config?.copy?.inputPlaceholder ?? \"Type your message…\";\n  textarea.className =\n    \"persona-w-full persona-min-h-[24px] persona-resize-none persona-border-none persona-bg-transparent persona-text-sm persona-text-persona-primary focus:persona-outline-none focus:persona-border-none persona-composer-textarea\";\n  textarea.rows = 1;\n\n  textarea.style.fontFamily =\n    'var(--persona-input-font-family, var(--persona-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif))';\n  textarea.style.fontWeight = \"var(--persona-input-font-weight, var(--persona-font-weight, 400))\";\n\n  // Auto-resize: expand up to 3 lines for the full composer (line-height ~20px\n  // for text-sm). The pill composer overrides this maxHeight after construction\n  // (allowing more growth in expanded mode), and the closure below honors\n  // whatever maxHeight is set at the time of the input event.\n  const defaultMaxLines = 3;\n  const lineHeight = 20;\n  textarea.style.maxHeight = `${defaultMaxLines * lineHeight}px`;\n  textarea.style.overflowY = \"auto\";\n\n  // Read maxHeight at event time so callers can change it after construction.\n  const readMaxHeight = (): number => {\n    const parsed = parseFloat(textarea.style.maxHeight);\n    return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultMaxLines * lineHeight;\n  };\n\n  const attachAutoResize = () => {\n    textarea.addEventListener(\"input\", () => {\n      textarea.style.height = \"auto\";\n      const newHeight = Math.min(textarea.scrollHeight, readMaxHeight());\n      textarea.style.height = `${newHeight}px`;\n    });\n  };\n\n  // Strip browser default focus rings: the composer wraps the textarea in\n  // its own surface, so the textarea itself must be visually transparent.\n  textarea.style.border = \"none\";\n  textarea.style.outline = \"none\";\n  textarea.style.borderWidth = \"0\";\n  textarea.style.borderStyle = \"none\";\n  textarea.style.borderColor = \"transparent\";\n  textarea.addEventListener(\"focus\", () => {\n    textarea.style.border = \"none\";\n    textarea.style.outline = \"none\";\n    textarea.style.borderWidth = \"0\";\n    textarea.style.borderStyle = \"none\";\n    textarea.style.borderColor = \"transparent\";\n    textarea.style.boxShadow = \"none\";\n  });\n  textarea.addEventListener(\"blur\", () => {\n    textarea.style.border = \"none\";\n    textarea.style.outline = \"none\";\n  });\n\n  return { textarea, attachAutoResize };\n};\n\nexport interface SendButtonParts {\n  button: HTMLButtonElement;\n  wrapper: HTMLElement;\n  /**\n   * Swap the button between its idle (\"send\") and streaming (\"stop\")\n   * appearances. In icon mode this swaps the SVG; in text mode it swaps\n   * the label. Tooltip text and aria-label update too.\n   */\n  setMode: (mode: \"send\" | \"stop\") => void;\n}\n\nexport const createSendButton = (config?: AgentWidgetConfig): SendButtonParts => {\n  const sendButtonConfig = config?.sendButton ?? {};\n  const useIcon = sendButtonConfig.useIcon ?? false;\n  const iconText = sendButtonConfig.iconText ?? \"↑\";\n  const iconName = sendButtonConfig.iconName;\n  const stopIconName = sendButtonConfig.stopIconName ?? \"square\";\n  const tooltipText = sendButtonConfig.tooltipText ?? \"Send message\";\n  const stopTooltipText = sendButtonConfig.stopTooltipText ?? \"Stop generating\";\n  const sendLabel = config?.copy?.sendButtonLabel ?? \"Send\";\n  const stopLabel = config?.copy?.stopButtonLabel ?? \"Stop\";\n  const showTooltip = sendButtonConfig.showTooltip ?? false;\n  const buttonSize = sendButtonConfig.size ?? \"40px\";\n  const backgroundColor = sendButtonConfig.backgroundColor;\n  const textColor = sendButtonConfig.textColor;\n\n  const wrapper = createElement(\"div\", \"persona-send-button-wrapper\");\n\n  const button = createNode(\"button\", {\n    className: cx(\n      \"persona-rounded-button disabled:persona-opacity-50 persona-cursor-pointer\",\n      useIcon\n        ? \"persona-flex persona-items-center persona-justify-center\"\n        : \"persona-bg-persona-accent persona-px-4 persona-py-2 persona-text-sm persona-font-semibold\",\n      // Icon mode without an explicit background falls back to the primary bg\n      // class; text mode without an explicit color falls back to white text.\n      useIcon && !backgroundColor && \"persona-bg-persona-primary\",\n      !useIcon && !textColor && \"persona-text-white\"\n    ),\n    attrs: { type: \"submit\", \"data-persona-composer-submit\": \"\" },\n    style: {\n      // Sizing is icon-mode-only (text mode is sized by its padding classes).\n      width: useIcon ? buttonSize : undefined,\n      height: useIcon ? buttonSize : undefined,\n      minWidth: useIcon ? buttonSize : undefined,\n      minHeight: useIcon ? buttonSize : undefined,\n      fontSize: useIcon ? \"18px\" : undefined,\n      lineHeight: useIcon ? \"1\" : undefined,\n      // Icon mode always sets a color; text mode only when textColor is given.\n      color: useIcon\n        ? textColor || \"var(--persona-button-primary-fg, #ffffff)\"\n        : textColor || undefined,\n      // backgroundColor is honored in icon mode only.\n      backgroundColor: useIcon ? backgroundColor || undefined : undefined,\n      borderWidth: sendButtonConfig.borderWidth || undefined,\n      borderStyle: sendButtonConfig.borderWidth ? \"solid\" : undefined,\n      borderColor: sendButtonConfig.borderColor || undefined,\n      paddingLeft: sendButtonConfig.paddingX || undefined,\n      paddingRight: sendButtonConfig.paddingX || undefined,\n      paddingTop: sendButtonConfig.paddingY || undefined,\n      paddingBottom: sendButtonConfig.paddingY || undefined,\n    },\n  });\n\n  // Both icons are pre-rendered so setMode can swap cheaply.\n  let sendIcon: SVGElement | null = null;\n  let stopIcon: SVGElement | null = null;\n\n  if (useIcon) {\n    const iconSize = parseFloat(buttonSize) || 24;\n    const iconColor = textColor?.trim() || \"currentColor\";\n\n    if (iconName) {\n      sendIcon = renderLucideIcon(iconName, iconSize, iconColor, 2);\n      if (sendIcon) {\n        button.appendChild(sendIcon);\n      } else {\n        button.textContent = iconText;\n      }\n    } else {\n      button.textContent = iconText;\n    }\n\n    stopIcon = renderLucideIcon(stopIconName, iconSize, iconColor, 2);\n  } else {\n    button.textContent = sendLabel;\n  }\n\n  let tooltip: HTMLElement | null = null;\n  if (showTooltip && tooltipText) {\n    tooltip = createElement(\"div\", \"persona-send-button-tooltip\");\n    tooltip.textContent = tooltipText;\n    wrapper.appendChild(tooltip);\n  }\n\n  button.setAttribute(\"aria-label\", tooltipText);\n  wrapper.appendChild(button);\n\n  let currentMode: \"send\" | \"stop\" = \"send\";\n  const setMode = (mode: \"send\" | \"stop\") => {\n    if (mode === currentMode) return;\n    currentMode = mode;\n    const label = mode === \"stop\" ? stopTooltipText : tooltipText;\n    button.setAttribute(\"aria-label\", label);\n    if (tooltip) {\n      tooltip.textContent = label;\n    }\n\n    if (useIcon) {\n      if (sendIcon && stopIcon) {\n        const next = mode === \"stop\" ? stopIcon : sendIcon;\n        // Replace whatever icon is currently mounted: the button only ever\n        // holds the single active icon. We use replaceChildren(next) rather\n        // than replaceChild(next, prev) against a captured `prev` reference:\n        // an external re-render/morph can swap the live icon child out from\n        // under us, detaching our captured node so `prev.parentNode !== button`.\n        // The old appendChild fallback then left BOTH icons mounted, which is\n        // how the send button ended up showing two stacked arrows after the\n        // first send→stop→send cycle.\n        button.replaceChildren(next);\n      }\n    } else {\n      button.textContent = mode === \"stop\" ? stopLabel : sendLabel;\n    }\n  };\n\n  return { button, wrapper, setMode };\n};\n\nexport interface MicButtonParts {\n  button: HTMLButtonElement;\n  wrapper: HTMLElement;\n}\n\n/**\n * Returns null when voice recognition is disabled or the browser doesn't\n * support either the Web Speech API or a Runtype voice provider.\n */\nexport const createMicButton = (config?: AgentWidgetConfig): MicButtonParts | null => {\n  const voiceRecognitionConfig = config?.voiceRecognition ?? {};\n  const voiceRecognitionEnabled = voiceRecognitionConfig.enabled === true;\n  if (!voiceRecognitionEnabled) return null;\n\n  const hasSpeechRecognition =\n    typeof window !== \"undefined\" &&\n    (typeof (window as unknown as { webkitSpeechRecognition?: unknown }).webkitSpeechRecognition !== \"undefined\" ||\n      typeof (window as unknown as { SpeechRecognition?: unknown }).SpeechRecognition !== \"undefined\");\n  const hasRuntypeProvider = voiceRecognitionConfig.provider?.type === \"runtype\";\n  const hasVoiceInput = hasSpeechRecognition || hasRuntypeProvider;\n  if (!hasVoiceInput) return null;\n\n  const buttonSize = config?.sendButton?.size ?? \"40px\";\n  const micIconName = voiceRecognitionConfig.iconName ?? \"mic\";\n  const micIconSize = voiceRecognitionConfig.iconSize ?? buttonSize;\n  const micIconSizeNum = parseFloat(micIconSize) || 24;\n  const micBackgroundColor =\n    voiceRecognitionConfig.backgroundColor ?? config?.sendButton?.backgroundColor;\n  const micIconColor = voiceRecognitionConfig.iconColor ?? config?.sendButton?.textColor;\n\n  const wrapper = createElement(\"div\", \"persona-send-button-wrapper\");\n  const button = createNode(\"button\", {\n    className:\n      \"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer\",\n    attrs: {\n      type: \"button\",\n      \"data-persona-composer-mic\": \"\",\n      \"aria-label\": \"Start voice recognition\",\n    },\n    style: {\n      width: micIconSize,\n      height: micIconSize,\n      minWidth: micIconSize,\n      minHeight: micIconSize,\n      fontSize: \"18px\",\n      lineHeight: \"1\",\n      color: micIconColor || \"var(--persona-text, #111827)\",\n      backgroundColor: micBackgroundColor || undefined,\n      borderWidth: voiceRecognitionConfig.borderWidth || undefined,\n      borderStyle: voiceRecognitionConfig.borderWidth ? \"solid\" : undefined,\n      borderColor: voiceRecognitionConfig.borderColor || undefined,\n      paddingLeft: voiceRecognitionConfig.paddingX || undefined,\n      paddingRight: voiceRecognitionConfig.paddingX || undefined,\n      paddingTop: voiceRecognitionConfig.paddingY || undefined,\n      paddingBottom: voiceRecognitionConfig.paddingY || undefined,\n    },\n  });\n\n  const iconColorValue = micIconColor || \"currentColor\";\n  const micIconSvg = renderLucideIcon(micIconName, micIconSizeNum, iconColorValue, 1.5);\n  if (micIconSvg) {\n    button.appendChild(micIconSvg);\n  } else {\n    button.textContent = \"🎤\";\n  }\n\n  wrapper.appendChild(button);\n\n  const micTooltipText = voiceRecognitionConfig.tooltipText ?? \"Start voice recognition\";\n  const showMicTooltip = voiceRecognitionConfig.showTooltip ?? false;\n  if (showMicTooltip && micTooltipText) {\n    const tooltip = createElement(\"div\", \"persona-send-button-tooltip\");\n    tooltip.textContent = micTooltipText;\n    wrapper.appendChild(tooltip);\n  }\n\n  return { button, wrapper };\n};\n\nexport interface AttachmentControlParts {\n  button: HTMLButtonElement;\n  wrapper: HTMLElement;\n  input: HTMLInputElement;\n  previewsContainer: HTMLElement;\n}\n\n/**\n * Returns null when attachments are disabled. Caller decides where to\n * place the previewsContainer (full composer puts it inside the form\n * above the textarea; pill composer floats it above the pill in a\n * separate row).\n */\nexport const createAttachmentControls = (config?: AgentWidgetConfig): AttachmentControlParts | null => {\n  const attachmentsConfig = config?.attachments ?? {};\n  if (attachmentsConfig.enabled !== true) return null;\n\n  const buttonSize = config?.sendButton?.size ?? \"40px\";\n\n  const previewsContainer = createElement(\n    \"div\",\n    \"persona-attachment-previews persona-flex persona-flex-wrap persona-gap-2 persona-mb-2\"\n  );\n  previewsContainer.setAttribute(\"data-persona-composer-attachment-previews\", \"\");\n  previewsContainer.style.display = \"none\";\n\n  const input = createElement(\"input\") as HTMLInputElement;\n  input.type = \"file\";\n  input.setAttribute(\"data-persona-composer-attachment-input\", \"\");\n  input.accept = (attachmentsConfig.allowedTypes ?? ALL_SUPPORTED_MIME_TYPES).join(\",\");\n  input.multiple = (attachmentsConfig.maxFiles ?? 4) > 1;\n  input.style.display = \"none\";\n  input.setAttribute(\"aria-label\", \"Attach files\");\n\n  const attachIconName = attachmentsConfig.buttonIconName ?? \"paperclip\";\n  const attachIconSize = buttonSize;\n  const buttonSizeNum = parseFloat(attachIconSize) || 40;\n  const attachIconSizeNum = Math.round(buttonSizeNum * 0.6);\n\n  const wrapper = createElement(\"div\", \"persona-send-button-wrapper\");\n  const button = createNode(\"button\", {\n    className:\n      \"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer persona-attachment-button\",\n    attrs: {\n      type: \"button\",\n      \"data-persona-composer-attachment-button\": \"\",\n      \"aria-label\": attachmentsConfig.buttonTooltipText ?? \"Attach file\",\n    },\n    style: {\n      width: attachIconSize,\n      height: attachIconSize,\n      minWidth: attachIconSize,\n      minHeight: attachIconSize,\n      fontSize: \"18px\",\n      lineHeight: \"1\",\n      backgroundColor: \"transparent\",\n      color: \"var(--persona-primary, #111827)\",\n      border: \"none\",\n      borderRadius: \"6px\",\n      transition: \"background-color 0.15s ease\",\n    },\n  });\n\n  button.addEventListener(\"mouseenter\", () => {\n    button.style.backgroundColor = \"var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.05))\";\n  });\n  button.addEventListener(\"mouseleave\", () => {\n    button.style.backgroundColor = \"transparent\";\n  });\n\n  const attachIconSvg = renderLucideIcon(attachIconName, attachIconSizeNum, \"currentColor\", 1.5);\n  if (attachIconSvg) {\n    button.appendChild(attachIconSvg);\n  } else {\n    button.textContent = \"📎\";\n  }\n\n  button.addEventListener(\"click\", (e) => {\n    e.preventDefault();\n    input.click();\n  });\n\n  wrapper.appendChild(button);\n\n  const attachTooltipText = attachmentsConfig.buttonTooltipText ?? \"Attach file\";\n  const tooltip = createElement(\"div\", \"persona-send-button-tooltip\");\n  tooltip.textContent = attachTooltipText;\n  wrapper.appendChild(tooltip);\n\n  return { button, wrapper, input, previewsContainer };\n};\n\nexport const createStatusText = (config?: AgentWidgetConfig): HTMLElement => {\n  const statusConfig = config?.statusIndicator ?? {};\n  const alignClass =\n    statusConfig.align === \"left\"\n      ? \"persona-text-left\"\n      : statusConfig.align === \"center\"\n        ? \"persona-text-center\"\n        : \"persona-text-right\";\n  const statusText = createElement(\n    \"div\",\n    `persona-mt-2 ${alignClass} persona-text-xs persona-text-persona-muted`\n  );\n  statusText.setAttribute(\"data-persona-composer-status\", \"\");\n\n  const isVisible = statusConfig.visible ?? true;\n  statusText.style.display = isVisible ? \"\" : \"none\";\n  const idleLabel = statusConfig.idleText ?? \"Online\";\n  if (statusConfig.idleLink) {\n    const link = createElement(\"a\") as HTMLAnchorElement;\n    link.href = statusConfig.idleLink;\n    link.target = \"_blank\";\n    link.rel = \"noopener noreferrer\";\n    link.textContent = idleLabel;\n    link.style.color = \"inherit\";\n    link.style.textDecoration = \"none\";\n    statusText.appendChild(link);\n  } else {\n    statusText.textContent = idleLabel;\n  }\n\n  return statusText;\n};\n\nexport const createSuggestionsRow = (): HTMLElement =>\n  createNode(\"div\", {\n    className: \"persona-mb-3 persona-flex persona-flex-wrap persona-gap-2\",\n    attrs: { \"data-persona-composer-suggestions\": \"\" },\n  });\n","import { createElement, createNode } from \"../utils/dom\";\nimport { AgentWidgetConfig, ContentPart } from \"../types\";\nimport {\n  createAttachmentControls,\n  createComposerTextarea,\n  createMicButton,\n  createSendButton,\n  createStatusText,\n  createSuggestionsRow,\n} from \"./composer-parts\";\n\nexport interface ComposerElements {\n  footer: HTMLElement;\n  suggestions: HTMLElement;\n  composerForm: HTMLFormElement;\n  textarea: HTMLTextAreaElement;\n  sendButton: HTMLButtonElement;\n  sendButtonWrapper: HTMLElement;\n  micButton: HTMLButtonElement | null;\n  micButtonWrapper: HTMLElement | null;\n  statusText: HTMLElement;\n  attachmentButton: HTMLButtonElement | null;\n  attachmentButtonWrapper: HTMLElement | null;\n  attachmentInput: HTMLInputElement | null;\n  attachmentPreviewsContainer: HTMLElement | null;\n  actionsRow: HTMLElement;\n  leftActions: HTMLElement;\n  rightActions: HTMLElement;\n  /**\n   * Swap the send button between its idle (\"send\") appearance and its\n   * streaming (\"stop\") appearance. In icon mode this swaps the SVG; in text\n   * mode it swaps the button label. Tooltip text is updated when a tooltip\n   * element is present.\n   */\n  setSendButtonMode: (mode: \"send\" | \"stop\") => void;\n}\n\nexport interface PendingAttachment {\n  id: string;\n  file: File;\n  previewUrl: string;\n  contentPart: ContentPart;\n}\n\nexport interface ComposerBuildContext {\n  config?: AgentWidgetConfig;\n  onSubmit?: (text: string) => void;\n  disabled?: boolean;\n}\n\n/**\n * Build the full footer + composer form (column-stacked card layout) for\n * the floating, docked, and inline-embed launcher modes. The pill variant\n * for `mountMode: \"composer-bar\"` lives in `pill-composer-builder.ts` and\n * shares the same low-level part factories from `composer-parts.ts`.\n */\nexport const buildComposer = (context: ComposerBuildContext): ComposerElements => {\n  const { config } = context;\n\n  const footer = createNode(\"div\", {\n    className:\n      \"persona-widget-footer persona-border-t-persona-divider persona-bg-persona-surface persona-px-6 persona-py-4\",\n    attrs: { \"data-persona-theme-zone\": \"composer\" },\n  });\n\n  const suggestions = createSuggestionsRow();\n\n  const composerForm = createNode(\"form\", {\n    className:\n      \"persona-widget-composer persona-flex persona-flex-col persona-gap-2 persona-rounded-2xl persona-border persona-border-gray-200 persona-bg-persona-input-background persona-px-4 persona-py-3\",\n    attrs: { \"data-persona-composer-form\": \"\" },\n    style: { outline: \"none\" },\n  });\n\n  const { textarea, attachAutoResize } = createComposerTextarea(config);\n  attachAutoResize();\n\n  const send = createSendButton(config);\n  const mic = createMicButton(config);\n  const attachment = createAttachmentControls(config);\n  const statusText = createStatusText(config);\n\n  // Layout (column):\n  //   row 1: attachment previews (above textarea, smaller)\n  //   row 2: textarea (full width)\n  //   row 3: actions (paperclip left, mic + send right)\n  if (attachment) {\n    attachment.previewsContainer.style.gap = \"8px\";\n    composerForm.append(attachment.previewsContainer, attachment.input);\n  }\n  composerForm.append(textarea);\n\n  // The bare class names (persona-widget-composer__actions / __left-actions /\n  // __right-actions) are stable CSS hooks. The pill composer reuses\n  // __left-actions / __right-actions as semantic markers in its grid.\n  const actionsRow = createNode(\"div\", {\n    className:\n      \"persona-widget-composer__actions persona-flex persona-items-center persona-justify-between persona-w-full\",\n    attrs: { \"data-persona-composer-actions\": \"\" },\n  });\n  const leftActions = createElement(\n    \"div\",\n    \"persona-widget-composer__left-actions persona-flex persona-items-center persona-gap-2\"\n  );\n  const rightActions = createElement(\n    \"div\",\n    \"persona-widget-composer__right-actions persona-flex persona-items-center persona-gap-1\"\n  );\n  if (attachment) leftActions.append(attachment.wrapper);\n  if (mic) rightActions.append(mic.wrapper);\n  rightActions.append(send.wrapper);\n  actionsRow.append(leftActions, rightActions);\n  composerForm.append(actionsRow);\n\n  // Click anywhere on the composer (other than the action buttons) → focus\n  // textarea so the click target feels like the whole input bar.\n  composerForm.addEventListener(\"click\", (e) => {\n    if (\n      e.target !== send.button &&\n      e.target !== send.wrapper &&\n      e.target !== mic?.button &&\n      e.target !== mic?.wrapper &&\n      e.target !== attachment?.button &&\n      e.target !== attachment?.wrapper\n    ) {\n      textarea.focus();\n    }\n  });\n\n  footer.append(suggestions, composerForm, statusText);\n\n  return {\n    footer,\n    suggestions,\n    composerForm,\n    textarea,\n    sendButton: send.button,\n    sendButtonWrapper: send.wrapper,\n    micButton: mic?.button ?? null,\n    micButtonWrapper: mic?.wrapper ?? null,\n    statusText,\n    attachmentButton: attachment?.button ?? null,\n    attachmentButtonWrapper: attachment?.wrapper ?? null,\n    attachmentInput: attachment?.input ?? null,\n    attachmentPreviewsContainer: attachment?.previewsContainer ?? null,\n    actionsRow,\n    leftActions,\n    rightActions,\n    setSendButtonMode: send.setMode,\n  };\n};\n","import { createElement, createNode } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { ComposerBuildContext, ComposerElements } from \"./composer-builder\";\nimport {\n  createAttachmentControls,\n  createComposerTextarea,\n  createMicButton,\n  createSendButton,\n  createStatusText,\n  createSuggestionsRow,\n} from \"./composer-parts\";\n\nexport interface PillPeekBanner {\n  /**\n   * The peek button itself: a chrome-less row that floats above the pill,\n   * showing a chat-bubble icon, a trailing-100-char preview of the most\n   * recent assistant message, and a chevron-up. Rendered hidden by default\n   * (opacity 0, pointer-events none); ui.ts toggles\n   * `.persona-pill-peek--visible` based on streaming/hover/open state.\n   */\n  root: HTMLButtonElement;\n  /** Wrapper around the trailing message preview text. */\n  textNode: HTMLElement;\n}\n\n/**\n * Build the peek banner for `launcher.mountMode: \"composer-bar\"`. The peek\n * is the user's path back into the expanded chat from the collapsed pill: * it fades in during streaming OR on composer hover, and clicking it opens\n * the panel. ui.ts owns visibility + content updates via\n * `syncComposerBarPeek`; this factory just produces the inert DOM shell.\n *\n * Placed in the panel between `container` and `footer` so it visually sits\n * just above the pill in the collapsed-state UI.\n */\nexport const buildPillPeekBanner = (): PillPeekBanner => {\n  const root = createNode(\"button\", {\n    className: \"persona-pill-peek\",\n    attrs: {\n      type: \"button\",\n      \"data-persona-pill-peek\": \"\",\n      \"aria-label\": \"Show conversation\",\n      tabindex: \"-1\",\n    },\n  });\n\n  const iconHolder = createElement(\"span\", \"persona-pill-peek__icon\");\n  const messageIcon = renderLucideIcon(\"message-square\", 16, \"currentColor\", 1.5);\n  if (messageIcon) {\n    iconHolder.appendChild(messageIcon);\n  }\n\n  const textNode = createElement(\"span\", \"persona-pill-peek__text\");\n\n  const caret = createElement(\"span\", \"persona-pill-peek__caret\");\n  const caretIcon = renderLucideIcon(\"chevron-up\", 16, \"currentColor\", 1.5);\n  if (caretIcon) {\n    caret.appendChild(caretIcon);\n  }\n\n  root.append(iconHolder, textNode, caret);\n  return { root, textNode };\n};\n\n/**\n * Single-row pill composer for `launcher.mountMode: \"composer-bar\"`.\n *\n * Same control factories as `buildComposer`: the only difference is the\n * layout shell + className. The form ships with `persona-pill-composer`\n * (no `persona-flex-col` / `persona-rounded-2xl` baggage), so the CSS\n * layout rules apply at normal specificity without `!important` fights.\n *\n * Returns the same `ComposerElements` shape as `buildComposer` so panel.ts\n * and ui.ts plumbing is unconditional past the choice of builder.\n *\n * Suggestions row + status text are built so plugin code that mutates them\n * keeps working and `bindComposerRefsFromFooter` finds them. CSS hides the\n * suggestions while collapsed and reveals them above the pill when expanded;\n * status text stays hidden because the pill UX has no room for it.\n *\n * Attachment previews float ABOVE the pill in their own row when\n * AttachmentManager toggles the previews container's `display` property\n * as items are added/removed.\n */\nexport const buildPillComposer = (context: ComposerBuildContext): ComposerElements => {\n  const { config } = context;\n\n  const footer = createNode(\"div\", {\n    className: \"persona-widget-footer persona-widget-footer--pill\",\n    attrs: { \"data-persona-theme-zone\": \"composer\" },\n  });\n\n  const suggestions = createSuggestionsRow();\n  const statusText = createStatusText(config);\n  statusText.style.display = \"none\";\n\n  const { textarea, attachAutoResize } = createComposerTextarea(config);\n  // Pill textarea: starts single-line, allowed to grow up to ~5 lines so\n  // expanded mode still supports multi-line composition. attachAutoResize\n  // reads max-height at event time, so this override flows through.\n  textarea.style.maxHeight = \"100px\";\n  attachAutoResize();\n\n  const send = createSendButton(config);\n  const mic = createMicButton(config);\n  const attachment = createAttachmentControls(config);\n\n  if (attachment) {\n    attachment.previewsContainer.classList.add(\"persona-pill-composer__previews\");\n  }\n\n  // Pill form: NO `persona-flex-col`. Only the marker classes that the rest\n  // of the codebase queries by name.\n  const composerForm = createNode(\"form\", {\n    className: \"persona-widget-composer persona-pill-composer\",\n    attrs: { \"data-persona-composer-form\": \"\" },\n    style: { outline: \"none\" },\n  });\n\n  // Three columns of the grid: [paperclip?] · textarea · mic + send.\n  // The empty leftActions wrapper still ships when attachments are off so\n  // the grid has a consistent first cell (auto width → collapses to 0).\n  const leftActions = createElement(\n    \"div\",\n    \"persona-widget-composer__left-actions persona-pill-composer__left\"\n  );\n  if (attachment) leftActions.append(attachment.wrapper);\n\n  const rightActions = createElement(\n    \"div\",\n    \"persona-widget-composer__right-actions persona-pill-composer__right\"\n  );\n  if (mic) rightActions.append(mic.wrapper);\n  rightActions.append(send.wrapper);\n\n  composerForm.addEventListener(\"click\", (e) => {\n    if (\n      e.target !== send.button &&\n      e.target !== send.wrapper &&\n      e.target !== mic?.button &&\n      e.target !== mic?.wrapper &&\n      e.target !== attachment?.button &&\n      e.target !== attachment?.wrapper\n    ) {\n      textarea.focus();\n    }\n  });\n\n  if (attachment) composerForm.append(attachment.input);\n  composerForm.append(leftActions, textarea, rightActions);\n\n  // Footer assembly:\n  //   [previews row, hidden until attachments exist]\n  //   [suggestions, visible only while expanded]\n  //   [pill form]\n  //   [hidden status]\n  if (attachment) footer.append(attachment.previewsContainer);\n  footer.append(suggestions, composerForm, statusText);\n\n  // The pill flattens left/right into the form's grid; there's no separate\n  // wrapper. Surface the form itself as `actionsRow` to satisfy the\n  // ComposerElements contract: downstream code only treats it as an\n  // opaque ref. The pill form intentionally carries no\n  // `data-persona-composer-actions` marker, so `bindComposerRefsFromFooter`\n  // finds no actions row in pill mode; that lookup writes to `_actionsRow`\n  // (the underscore prefix marks it as soft-optional).\n  const actionsRow = composerForm;\n\n  return {\n    footer,\n    suggestions,\n    composerForm,\n    textarea,\n    sendButton: send.button,\n    sendButtonWrapper: send.wrapper,\n    micButton: mic?.button ?? null,\n    micButtonWrapper: mic?.wrapper ?? null,\n    statusText,\n    attachmentButton: attachment?.button ?? null,\n    attachmentButtonWrapper: attachment?.wrapper ?? null,\n    attachmentInput: attachment?.input ?? null,\n    attachmentPreviewsContainer: attachment?.previewsContainer ?? null,\n    actionsRow,\n    leftActions,\n    rightActions,\n    setSendButtonMode: send.setMode,\n  };\n};\n","import { createElement, createNode } from \"../utils/dom\";\nimport { DEFAULT_FLOATING_LAUNCHER_WIDTH } from \"../defaults\";\nimport { AgentWidgetConfig } from \"../types\";\nimport { positionMap } from \"../utils/positioning\";\nimport { isComposerBarMountMode, isDockedMountMode } from \"../utils/dock\";\nimport { DEFAULT_OVERLAY_Z_INDEX } from \"../utils/constants\";\nimport { buildHeader, attachHeaderToContainer, HeaderElements } from \"./header-builder\";\nimport { buildHeaderWithLayout } from \"./header-layouts\";\nimport { createCloseButton, createClearChatButton } from \"./header-parts\";\nimport { buildComposer, ComposerElements } from \"./composer-builder\";\nimport { buildPillComposer, buildPillPeekBanner } from \"./pill-composer-builder\";\n\nexport interface PanelWrapper {\n  wrapper: HTMLElement;\n  panel: HTMLElement;\n  /**\n   * Composer-bar mode only: viewport-fixed sibling of `wrapper` that owns\n   * the persistent pill (`footer`) and peek banner. Lives outside the\n   * wrapper so it never inherits the wrapper's geometry transitions:   * critical for modal mode where the wrapper is `transform: translate(-50%, -50%)`\n   * (a transformed ancestor establishes a containing block for `position: fixed`\n   * descendants, which would trap the pill inside the wrapper otherwise).\n   */\n  pillRoot?: HTMLElement;\n}\n\nexport const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {\n  const launcherEnabled = config?.launcher?.enabled ?? true;\n  const dockedMode = isDockedMountMode(config);\n  const composerBarMode = isComposerBarMountMode(config);\n\n  if (composerBarMode) {\n    const cb = config?.launcher?.composerBar ?? {};\n    // Geometry (left/transform/bottom/top/width/max-width) is intentionally\n    // NOT set here: it's owned entirely by `applyComposerBarGeometry()` in\n    // ui.ts so that collapsed → expanded transitions can clear the previous\n    // state's inline styles cleanly. Setting geometry here would persist\n    // across state changes and override the per-state values, which\n    // previously caused expanded panels to render at collapsed dimensions.\n    const wrapper = createElement(\n      \"div\",\n      \"persona-widget-wrapper persona-fixed persona-transition\"\n    );\n    wrapper.setAttribute(\"data-persona-composer-bar\", \"\");\n    wrapper.dataset.state = \"collapsed\";\n    wrapper.dataset.expandedSize = cb.expandedSize ?? \"anchored\";\n    wrapper.style.zIndex = String(\n      config?.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX\n    );\n\n    const panel = createElement(\n      \"div\",\n      \"persona-widget-panel persona-relative persona-flex persona-flex-1 persona-min-h-0 persona-flex-col\"\n    );\n    panel.style.width = \"100%\";\n    wrapper.appendChild(panel);\n\n    // Pill lives in a separate viewport-fixed sibling: see PanelWrapper\n    // docs above. ui.ts appends `peekBanner` and `footer` here, then\n    // appends pillRoot to `mount` immediately after the wrapper.\n    const pillRoot = createElement(\"div\", \"persona-widget-pill-root\");\n    pillRoot.setAttribute(\"data-persona-composer-bar\", \"\");\n    pillRoot.dataset.state = \"collapsed\";\n    pillRoot.dataset.expandedSize = cb.expandedSize ?? \"anchored\";\n    pillRoot.style.zIndex = String(\n      config?.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX\n    );\n\n    return { wrapper, panel, pillRoot };\n  }\n\n  if (dockedMode) {\n    const wrapper = createElement(\n      \"div\",\n      \"persona-relative persona-h-full persona-w-full persona-flex persona-flex-1 persona-min-h-0 persona-flex-col\"\n    );\n    const panel = createElement(\n      \"div\",\n      \"persona-relative persona-h-full persona-w-full persona-flex persona-flex-1 persona-min-h-0 persona-flex-col\"\n    );\n\n    wrapper.appendChild(panel);\n    return { wrapper, panel };\n  }\n\n  if (!launcherEnabled) {\n    // For inline embed mode, use flex layout to ensure the widget fills its container\n    // and only the chat messages area scrolls\n    const wrapper = createElement(\n      \"div\",\n      \"persona-relative persona-h-full persona-flex persona-flex-col persona-flex-1 persona-min-h-0\"\n    );\n    const panel = createElement(\n      \"div\",\n      \"persona-relative persona-flex-1 persona-flex persona-flex-col persona-min-h-0\"\n    );\n    \n    // Apply width from config, defaulting to 100% for inline embed mode\n    const inlineWidth = config?.launcher?.width ?? \"100%\";\n    wrapper.style.width = inlineWidth;\n    panel.style.width = \"100%\";\n    \n    wrapper.appendChild(panel);\n    return { wrapper, panel };\n  }\n\n  const launcher = config?.launcher ?? {};\n  const position =\n    launcher.position && positionMap[launcher.position]\n      ? positionMap[launcher.position]\n      : positionMap[\"bottom-right\"];\n\n  const wrapper = createElement(\n    \"div\",\n    `persona-widget-wrapper persona-fixed ${position} persona-transition`\n  );\n  wrapper.style.zIndex = String(config?.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX);\n\n  const panel = createElement(\n    \"div\",\n    \"persona-widget-panel persona-relative persona-min-h-[320px]\"\n  );\n  const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;\n  const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;\n  panel.style.width = width;\n  panel.style.maxWidth = width;\n\n  wrapper.appendChild(panel);\n  return { wrapper, panel };\n};\n\nexport interface PanelElements {\n  container: HTMLElement;\n  body: HTMLElement;\n  messagesWrapper: HTMLElement;\n  /**\n   * Absolute-positioned slot above the composer footer. Interactive sheets\n   * (e.g. the answer-pill sheet for the ask_user_question tool) mount here\n   * so they slide in without reflowing the chat transcript.\n   */\n  composerOverlay: HTMLElement;\n  suggestions: HTMLElement;\n  textarea: HTMLTextAreaElement;\n  sendButton: HTMLButtonElement;\n  sendButtonWrapper: HTMLElement;\n  micButton: HTMLButtonElement | null;\n  micButtonWrapper: HTMLElement | null;\n  composerForm: HTMLFormElement;\n  statusText: HTMLElement;\n  introTitle: HTMLElement;\n  introSubtitle: HTMLElement;\n  closeButton: HTMLButtonElement;\n  closeButtonWrapper: HTMLElement;\n  clearChatButton: HTMLButtonElement | null;\n  clearChatButtonWrapper: HTMLElement | null;\n  iconHolder: HTMLElement;\n  headerTitle: HTMLElement;\n  headerSubtitle: HTMLElement;\n  // Exposed for potential header replacement\n  header: HTMLElement;\n  footer: HTMLElement;\n  // Attachment elements\n  attachmentButton: HTMLButtonElement | null;\n  attachmentButtonWrapper: HTMLElement | null;\n  attachmentInput: HTMLInputElement | null;\n  attachmentPreviewsContainer: HTMLElement | null;\n  // Actions row layout elements\n  actionsRow: HTMLElement;\n  leftActions: HTMLElement;\n  rightActions: HTMLElement;\n  /** Swap the send button between its send and stop appearances. */\n  setSendButtonMode: (mode: \"send\" | \"stop\") => void;\n  /**\n   * Composer-bar peek banner: the chrome-less row above the pill that\n   * shows a trailing preview of the most recent assistant message.\n   * Undefined for non-composer-bar modes.\n   */\n  peekBanner?: HTMLButtonElement;\n  peekTextNode?: HTMLElement;\n}\n\n/**\n * Composer-bar panel: minimal close-only header (× in the top-right of the\n * chat chrome). The pill (`footer`) and `peekBanner` are NOT children of\n * the panel: caller (`ui.ts`) appends them to the `pillRoot` returned by\n * `createWrapper`, which is a viewport-fixed sibling of the wrapper.\n * This decouples the pill from the wrapper's geometry transitions (so the\n * pill stays anchored at the viewport bottom regardless of expanded mode).\n */\nconst buildComposerBarPanel = (\n  config: AgentWidgetConfig | undefined,\n  showClose: boolean,\n): PanelElements => {\n  // Container = the chat panel chrome (border + bg + radius + shadow applied\n  // inline by `applyFullHeightStyles` in ui.ts). `position: relative` anchors\n  // the absolute close button + composerOverlay.\n  const container = createElement(\n    \"div\",\n    \"persona-widget-container persona-relative persona-flex persona-flex-1 persona-min-h-0 persona-flex-col persona-text-persona-primary\"\n  );\n  container.setAttribute(\"data-persona-theme-zone\", \"container\");\n\n  // Minimal header: just an absolutely-positioned close button.\n  // The wrapper uses inline styles (top/right/z-index values) because the\n  // widget's hand-authored CSS doesn't ship every Tailwind utility.\n  // Composer-bar's defaults are roughly half the floating-launcher's\n  // (button 16px, icon 14px) to match the minimal aesthetic; the user can\n  // still override via `launcher.closeButtonSize`.\n  const { button: closeButton, wrapper: closeButtonWrapper } = createCloseButton(\n    config,\n    {\n      showClose,\n      wrapperClassName: \"persona-composer-bar-close\",\n      buttonSize: \"16px\",\n      iconSize: \"14px\",\n    }\n  );\n  closeButtonWrapper.style.position = \"absolute\";\n  closeButtonWrapper.style.top = \"8px\";\n  closeButtonWrapper.style.right = \"8px\";\n  closeButtonWrapper.style.zIndex = \"10\";\n\n  // Clear / \"start over\" button: sits immediately to the left of the close\n  // button in the panel chrome's top-right corner. Same minimal sizing as\n  // the close icon (16px button, 14px icon) so the two read as a paired\n  // action group rather than a header strip. Wired by `setupClearChatButton()`\n  // in ui.ts via the `clearChatButton` field on PanelElements.\n  //\n  // Right offset = close button right (8px) + close button width (16px) +\n  // 8px inter-button gap = 32px.\n  const clearChatEnabled = config?.launcher?.clearChat?.enabled ?? true;\n  let clearChatButton: HTMLButtonElement | null = null;\n  let clearChatButtonWrapper: HTMLElement | null = null;\n  if (clearChatEnabled) {\n    const parts = createClearChatButton(config, {\n      wrapperClassName: \"persona-composer-bar-clear-chat\",\n      buttonSize: \"16px\",\n      iconSize: \"14px\",\n    });\n    clearChatButton = parts.button;\n    clearChatButtonWrapper = parts.wrapper;\n    clearChatButtonWrapper.style.position = \"absolute\";\n    clearChatButtonWrapper.style.top = \"8px\";\n    clearChatButtonWrapper.style.right = \"32px\";\n    clearChatButtonWrapper.style.zIndex = \"10\";\n  }\n  // Placeholder header element so PanelElements.header exists (some downstream\n  // code reads it). It carries `data-persona-widget-header` for plugin /\n  // selector parity but renders nothing visible: the close button is the only\n  // header chrome in composer-bar mode.\n  const headerPlaceholder = createNode(\"span\", {\n    className: \"persona-widget-header\",\n    attrs: { \"data-persona-theme-zone\": \"header\" },\n    style: { display: \"none\" },\n  });\n\n  // Body: extra top padding (set inline so the hand-authored widget.css\n  // doesn't need a `pt-12` utility) so the absolute close button doesn't\n  // overlap the welcome card / first message.\n  const body = createNode(\"div\", {\n    className:\n      \"persona-widget-body persona-flex persona-flex-1 persona-min-h-0 persona-flex-col persona-gap-6 persona-overflow-y-auto persona-bg-persona-container persona-px-6 persona-py-6\",\n    attrs: { id: \"persona-scroll-container\", \"data-persona-theme-zone\": \"messages\" },\n    style: { paddingTop: \"48px\" },\n  });\n  // Reserve the scrollbar gutter so the transcript doesn't shift horizontally\n  // when streaming content first overflows and the scrollbar appears.\n  body.style.setProperty(\"scrollbar-gutter\", \"stable\");\n\n  const introTitle = createNode(\"h2\", {\n    className: \"persona-text-lg persona-font-semibold persona-text-persona-primary\",\n    text: config?.copy?.welcomeTitle ?? \"Hello 👋\",\n  });\n  const introSubtitle = createNode(\"p\", {\n    className: \"persona-mt-2 persona-text-sm persona-text-persona-muted\",\n    text:\n      config?.copy?.welcomeSubtitle ??\n      \"Ask anything about your account or products.\",\n  });\n  const introCard = createNode(\n    \"div\",\n    {\n      className: \"persona-rounded-2xl persona-p-6\",\n      attrs: { \"data-persona-intro-card\": \"\" },\n      style: {\n        background:\n          \"var(--persona-intro-card-bg, var(--persona-surface, #ffffff))\",\n        boxShadow:\n          \"var(--persona-intro-card-shadow, 0 5px 15px rgba(15, 23, 42, 0.08))\",\n      },\n    },\n    introTitle,\n    introSubtitle\n  );\n\n  const messagesWrapper = createElement(\n    \"div\",\n    \"persona-flex persona-flex-col persona-gap-3\"\n  );\n  const contentMaxWidth = config?.layout?.contentMaxWidth;\n  if (contentMaxWidth) {\n    messagesWrapper.style.maxWidth = contentMaxWidth;\n    messagesWrapper.style.marginLeft = \"auto\";\n    messagesWrapper.style.marginRight = \"auto\";\n    messagesWrapper.style.width = \"100%\";\n  }\n\n  const showWelcomeCard = config?.copy?.showWelcomeCard !== false;\n  if (!showWelcomeCard) {\n    introCard.style.display = \"none\";\n    body.classList.remove(\"persona-gap-6\");\n    body.classList.add(\"persona-gap-3\");\n  }\n  body.append(introCard, messagesWrapper);\n\n  // Composer overlay (interactive sheets like ask_user_question slide up here).\n  // Anchored to the bottom of the container (which is `position: relative`),\n  // so sheets render at the bottom of the chat chrome: just above the gap +\n  // pill that sit below the container.\n  const composerOverlay = createNode(\"div\", {\n    className: \"persona-composer-overlay persona-pointer-events-none\",\n    attrs: { \"data-persona-composer-overlay\": \"\" },\n    style: { position: \"absolute\", left: \"0\", right: \"0\", bottom: \"0\", zIndex: \"20\" },\n  });\n\n  // Pill composer: caller appends as a sibling of container in the panel.\n  const composerElements: ComposerElements = buildPillComposer({ config });\n\n  // Peek banner: caller inserts as a sibling of container/footer between\n  // them in the panel. Hidden by default; ui.ts toggles visibility.\n  const { root: peekBanner, textNode: peekTextNode } = buildPillPeekBanner();\n\n  // Container = [close button (absolute), placeholder header, body, overlay].\n  // Footer (pill) is intentionally NOT appended here.\n  container.append(headerPlaceholder, closeButtonWrapper, body, composerOverlay);\n  if (clearChatButtonWrapper) {\n    container.appendChild(clearChatButtonWrapper);\n  }\n\n  return {\n    container,\n    body,\n    messagesWrapper,\n    composerOverlay,\n    suggestions: composerElements.suggestions,\n    textarea: composerElements.textarea,\n    sendButton: composerElements.sendButton,\n    sendButtonWrapper: composerElements.sendButtonWrapper,\n    micButton: composerElements.micButton,\n    micButtonWrapper: composerElements.micButtonWrapper,\n    composerForm: composerElements.composerForm,\n    statusText: composerElements.statusText,\n    introTitle,\n    introSubtitle,\n    closeButton,\n    closeButtonWrapper,\n    clearChatButton,\n    clearChatButtonWrapper,\n    iconHolder: createElement(\"span\"),\n    headerTitle: createElement(\"span\"),\n    headerSubtitle: createElement(\"span\"),\n    header: headerPlaceholder,\n    footer: composerElements.footer,\n    attachmentButton: composerElements.attachmentButton,\n    attachmentButtonWrapper: composerElements.attachmentButtonWrapper,\n    attachmentInput: composerElements.attachmentInput,\n    attachmentPreviewsContainer: composerElements.attachmentPreviewsContainer,\n    actionsRow: composerElements.actionsRow,\n    leftActions: composerElements.leftActions,\n    rightActions: composerElements.rightActions,\n    setSendButtonMode: composerElements.setSendButtonMode,\n    peekBanner,\n    peekTextNode,\n  };\n};\n\nexport const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelElements => {\n  // Composer-bar mode renders a purpose-built panel: minimal close-only\n  // header (small × in the top-right of the chat panel chrome), and the\n  // pill (footer) is a SIBLING of the container so it stays visible/usable\n  // when the chat is expanded above it.\n  if (isComposerBarMountMode(config)) {\n    return buildComposerBarPanel(config, showClose);\n  }\n\n  const container = createNode(\"div\", {\n    className:\n      \"persona-widget-container persona-flex persona-h-full persona-w-full persona-flex-1 persona-min-h-0 persona-flex-col persona-text-persona-primary persona-bg-persona-surface persona-rounded-2xl persona-overflow-hidden persona-border persona-border-persona-border\",\n    attrs: { \"data-persona-theme-zone\": \"container\" },\n  });\n\n  // Build header using layout config if available, otherwise use standard builder\n  const headerLayoutConfig = config?.layout?.header;\n  const showHeader = config?.layout?.showHeader !== false; // default to true\n  const headerElements: HeaderElements = headerLayoutConfig\n    ? buildHeaderWithLayout(config!, headerLayoutConfig, { showClose })\n    : buildHeader({ config, showClose });\n\n  // Build body with intro card and messages wrapper\n  const body = createNode(\"div\", {\n    className:\n      \"persona-widget-body persona-flex persona-flex-1 persona-min-h-0 persona-flex-col persona-gap-6 persona-overflow-y-auto persona-bg-persona-container persona-px-6 persona-py-6\",\n    attrs: { id: \"persona-scroll-container\", \"data-persona-theme-zone\": \"messages\" },\n  });\n  // Reserve the scrollbar gutter so the transcript doesn't shift horizontally\n  // when streaming content first overflows and the scrollbar appears.\n  body.style.setProperty(\"scrollbar-gutter\", \"stable\");\n\n  const introTitle = createNode(\"h2\", {\n    className: \"persona-text-lg persona-font-semibold persona-text-persona-primary\",\n    text: config?.copy?.welcomeTitle ?? \"Hello 👋\",\n  });\n  const introSubtitle = createNode(\"p\", {\n    className: \"persona-mt-2 persona-text-sm persona-text-persona-muted\",\n    text:\n      config?.copy?.welcomeSubtitle ??\n      \"Ask anything about your account or products.\",\n  });\n  // Background and box-shadow flow through the themable `components.introCard`\n  // tokens (--persona-intro-card-bg / --persona-intro-card-shadow), each\n  // falling back to its legacy value (`--persona-surface` / `persona-shadow-sm`)\n  // when no token is set. Docked mode keeps a flat shadow by default.\n  const introCard = createNode(\n    \"div\",\n    {\n      className: \"persona-rounded-2xl persona-p-6\",\n      attrs: { \"data-persona-intro-card\": \"\" },\n      style: {\n        background:\n          \"var(--persona-intro-card-bg, var(--persona-surface, #ffffff))\",\n        boxShadow: isDockedMountMode(config)\n          ? \"none\"\n          : \"var(--persona-intro-card-shadow, 0 5px 15px rgba(15, 23, 42, 0.08))\",\n      },\n    },\n    introTitle,\n    introSubtitle\n  );\n\n  const messagesWrapper = createElement(\n    \"div\",\n    \"persona-flex persona-flex-col persona-gap-3\"\n  );\n\n  const contentMaxWidth = config?.layout?.contentMaxWidth;\n  if (contentMaxWidth) {\n    messagesWrapper.style.maxWidth = contentMaxWidth;\n    messagesWrapper.style.marginLeft = \"auto\";\n    messagesWrapper.style.marginRight = \"auto\";\n    messagesWrapper.style.width = \"100%\";\n  }\n\n  const showWelcomeCard = config?.copy?.showWelcomeCard !== false;\n  if (!showWelcomeCard) {\n    introCard.style.display = \"none\";\n    body.classList.remove(\"persona-gap-6\");\n    body.classList.add(\"persona-gap-3\");\n  }\n  body.append(introCard, messagesWrapper);\n\n  // composer-bar mode early-returned above with its own pill builder; this\n  // path always uses the standard column-stacked composer card.\n  const composerElements: ComposerElements = buildComposer({ config });\n  const showFooter = config?.layout?.showFooter !== false; // default to true\n\n  // Assemble container with header, body, and footer\n  if (showHeader) {\n    attachHeaderToContainer(container, headerElements, config);\n  } else {\n    // Hide header completely\n    headerElements.header.style.display = 'none';\n    attachHeaderToContainer(container, headerElements, config);\n  }\n  \n  container.append(body);\n  \n  // Composer overlay slot: sits between body and footer, absolutely positioned\n  // above the composer so sheets (e.g. the ask_user_question answer-pill sheet)\n  // can slide up without reflowing the chat transcript above. Uses inline\n  // styles for left/right/bottom because widget.css is hand-authored and\n  // doesn't ship `.persona-left-0` / `.persona-right-0` rules: without\n  // them the overlay shrink-wraps to content and collapses the sheet width.\n  // zIndex 20 sits above .persona-scroll-to-bottom-indicator (z-index 10,\n  // sibling in the container) so suggestion chips and the ask-user-question\n  // sheet are not covered by the \"jump to latest\" button.\n  const composerOverlay = createNode(\"div\", {\n    className: \"persona-composer-overlay persona-pointer-events-none\",\n    attrs: { \"data-persona-composer-overlay\": \"\" },\n    style: { position: \"absolute\", left: \"0\", right: \"0\", bottom: \"0\", zIndex: \"20\" },\n  });\n\n  if (showFooter) {\n    container.append(composerElements.footer);\n  } else {\n    // Hide footer completely\n    composerElements.footer.style.display = 'none';\n    container.append(composerElements.footer);\n  }\n\n  // Append overlay last so it stacks above the footer / body content.\n  container.append(composerOverlay);\n\n  return {\n    container,\n    body,\n    messagesWrapper,\n    composerOverlay,\n    suggestions: composerElements.suggestions,\n    textarea: composerElements.textarea,\n    sendButton: composerElements.sendButton,\n    sendButtonWrapper: composerElements.sendButtonWrapper,\n    micButton: composerElements.micButton,\n    micButtonWrapper: composerElements.micButtonWrapper,\n    composerForm: composerElements.composerForm,\n    statusText: composerElements.statusText,\n    introTitle,\n    introSubtitle,\n    closeButton: headerElements.closeButton,\n    closeButtonWrapper: headerElements.closeButtonWrapper,\n    clearChatButton: headerElements.clearChatButton,\n    clearChatButtonWrapper: headerElements.clearChatButtonWrapper,\n    iconHolder: headerElements.iconHolder,\n    headerTitle: headerElements.headerTitle,\n    headerSubtitle: headerElements.headerSubtitle,\n    header: headerElements.header,\n    footer: composerElements.footer,\n    // Attachment elements\n    attachmentButton: composerElements.attachmentButton,\n    attachmentButtonWrapper: composerElements.attachmentButtonWrapper,\n    attachmentInput: composerElements.attachmentInput,\n    attachmentPreviewsContainer: composerElements.attachmentPreviewsContainer,\n    // Actions row layout elements\n    actionsRow: composerElements.actionsRow,\n    leftActions: composerElements.leftActions,\n    rightActions: composerElements.rightActions,\n    setSendButtonMode: composerElements.setSendButtonMode\n  };\n};\n\n// Re-export builder types and functions for plugin use\nexport { buildHeader, buildComposer, attachHeaderToContainer };\nexport type { HeaderElements, HeaderBuildContext } from \"./header-builder\";\nexport type { ComposerElements, ComposerBuildContext } from \"./composer-builder\";\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetConfig } from \"../types\";\nimport { positionMap } from \"../utils/positioning\";\nimport { isDockedMountMode } from \"../utils/dock\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { DEFAULT_OVERLAY_Z_INDEX } from \"../utils/constants\";\n\nexport interface LauncherButton {\n  element: HTMLButtonElement;\n  update: (config: AgentWidgetConfig) => void;\n  destroy: () => void;\n}\n\nexport const createLauncherButton = (\n  config: AgentWidgetConfig | undefined,\n  onToggle: () => void\n): LauncherButton => {\n  const button = createElement(\"button\") as HTMLButtonElement;\n  button.type = \"button\";\n  button.innerHTML = `\n    <span class=\"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-bg-persona-primary persona-text-white\" data-role=\"launcher-icon\">💬</span>\n    <img data-role=\"launcher-image\" class=\"persona-rounded-full persona-object-cover\" alt=\"\" style=\"display:none\" />\n    <span class=\"persona-flex persona-min-w-0 persona-flex-1 persona-flex-col persona-items-start persona-text-left\">\n      <span class=\"persona-block persona-w-full persona-truncate persona-text-sm persona-font-semibold persona-text-persona-primary\" data-role=\"launcher-title\"></span>\n      <span class=\"persona-block persona-w-full persona-truncate persona-text-xs persona-text-persona-muted\" data-role=\"launcher-subtitle\"></span>\n    </span>\n    <span class=\"persona-ml-2 persona-grid persona-place-items-center persona-rounded-full persona-bg-persona-primary persona-text-persona-call-to-action\" data-role=\"launcher-call-to-action-icon\">↗</span>\n  `;\n  button.addEventListener(\"click\", onToggle);\n\n  const update = (newConfig: AgentWidgetConfig) => {\n    const launcher = newConfig.launcher ?? {};\n    const dockedMode = isDockedMountMode(newConfig);\n\n    const titleEl = button.querySelector(\"[data-role='launcher-title']\");\n    if (titleEl) {\n      const t = launcher.title ?? \"Chat Assistant\";\n      titleEl.textContent = t;\n      titleEl.setAttribute(\"title\", t);\n    }\n\n    const subtitleEl = button.querySelector(\"[data-role='launcher-subtitle']\");\n    if (subtitleEl) {\n      const s = launcher.subtitle ?? \"Here to help you get answers fast\";\n      subtitleEl.textContent = s;\n      subtitleEl.setAttribute(\"title\", s);\n    }\n\n    // Hide/show text container\n    const textContainer = button.querySelector(\".persona-flex-col\");\n    if (textContainer) {\n      if (launcher.textHidden || dockedMode) {\n        (textContainer as HTMLElement).style.display = \"none\";\n      } else {\n        (textContainer as HTMLElement).style.display = \"\";\n      }\n    }\n\n    const icon = button.querySelector<HTMLSpanElement>(\"[data-role='launcher-icon']\");\n    if (icon) {\n      if (launcher.agentIconHidden) {\n        icon.style.display = \"none\";\n      } else {\n        const iconSize = launcher.agentIconSize ?? \"40px\";\n        icon.style.height = iconSize;\n        icon.style.width = iconSize;\n\n        // Optional custom background color for the agent icon circle. When set,\n        // override the default primary-color background; otherwise restore it.\n        if (launcher.agentIconBackgroundColor) {\n          icon.style.backgroundColor = launcher.agentIconBackgroundColor;\n          icon.classList.remove(\"persona-bg-persona-primary\");\n        } else {\n          icon.style.backgroundColor = \"\";\n          icon.classList.add(\"persona-bg-persona-primary\");\n        }\n\n        // Clear existing content\n        icon.innerHTML = \"\";\n        \n        // Render icon based on priority: Lucide icon > iconUrl > agentIconText\n        if (launcher.agentIconName) {\n          // Use Lucide icon\n          const iconSizeNum = parseFloat(iconSize) || 24;\n          const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, \"var(--persona-text-inverse, #ffffff)\", 2);\n          if (iconSvg) {\n            icon.appendChild(iconSvg);\n            icon.style.display = \"\";\n          } else {\n            // Fallback to agentIconText if Lucide icon fails\n            icon.textContent = launcher.agentIconText ?? \"💬\";\n            icon.style.display = \"\";\n          }\n        } else if (launcher.iconUrl) {\n          // Use image URL - hide icon span and show img\n          icon.style.display = \"none\";\n        } else {\n          // Use text/emoji\n          icon.textContent = launcher.agentIconText ?? \"💬\";\n          icon.style.display = \"\";\n        }\n      }\n    }\n\n    const img = button.querySelector<HTMLImageElement>(\"[data-role='launcher-image']\");\n    if (img) {\n      const iconSize = launcher.agentIconSize ?? \"40px\";\n      img.style.height = iconSize;\n      img.style.width = iconSize;\n      if (launcher.iconUrl && !launcher.agentIconName && !launcher.agentIconHidden) {\n        // Only show image if not using Lucide icon and not hidden\n        img.src = launcher.iconUrl;\n        img.style.display = \"block\";\n      } else {\n        img.style.display = \"none\";\n      }\n    }\n\n    const callToActionIconEl = button.querySelector<HTMLSpanElement>(\"[data-role='launcher-call-to-action-icon']\");\n    if (callToActionIconEl) {\n      const callToActionIconSize = launcher.callToActionIconSize ?? \"32px\";\n      callToActionIconEl.style.height = callToActionIconSize;\n      callToActionIconEl.style.width = callToActionIconSize;\n      \n      // Apply background color if configured\n      if (launcher.callToActionIconBackgroundColor) {\n        callToActionIconEl.style.backgroundColor = launcher.callToActionIconBackgroundColor;\n        callToActionIconEl.classList.remove(\"persona-bg-persona-primary\");\n      } else {\n        callToActionIconEl.style.backgroundColor = \"\";\n        callToActionIconEl.classList.add(\"persona-bg-persona-primary\");\n      }\n\n      // Apply foreground/icon color if configured\n      if (launcher.callToActionIconColor) {\n        callToActionIconEl.style.color = launcher.callToActionIconColor;\n        callToActionIconEl.classList.remove(\"persona-text-persona-call-to-action\");\n      } else {\n        callToActionIconEl.style.color = \"\";\n        callToActionIconEl.classList.add(\"persona-text-persona-call-to-action\");\n      }\n      \n      // Calculate padding to adjust icon size\n      let paddingTotal = 0;\n      if (launcher.callToActionIconPadding) {\n        callToActionIconEl.style.boxSizing = \"border-box\";\n        callToActionIconEl.style.padding = launcher.callToActionIconPadding;\n        // Parse padding value to calculate total padding (padding applies to both sides)\n        const paddingValue = parseFloat(launcher.callToActionIconPadding) || 0;\n        paddingTotal = paddingValue * 2; // padding on both sides\n      } else {\n        callToActionIconEl.style.boxSizing = \"\";\n        callToActionIconEl.style.padding = \"\";\n      }\n      \n      if (launcher.callToActionIconHidden) {\n        callToActionIconEl.style.display = \"none\";\n      } else {\n        callToActionIconEl.style.display = dockedMode ? \"none\" : \"\";\n        \n        // Clear existing content\n        callToActionIconEl.innerHTML = \"\";\n        \n        // Use Lucide icon if provided, otherwise fall back to text\n        if (launcher.callToActionIconName) {\n          // Calculate actual icon size by subtracting padding\n          const containerSize = parseFloat(callToActionIconSize) || 24;\n          const iconSize = Math.max(containerSize - paddingTotal, 8); // Ensure minimum size of 8px\n          const iconSvg = renderLucideIcon(launcher.callToActionIconName, iconSize, \"currentColor\", 2);\n          if (iconSvg) {\n            callToActionIconEl.appendChild(iconSvg);\n          } else {\n            // Fallback to text if icon fails to render\n            callToActionIconEl.textContent = launcher.callToActionIconText ?? \"↗\";\n          }\n        } else {\n          callToActionIconEl.textContent = launcher.callToActionIconText ?? \"↗\";\n        }\n      }\n    }\n\n    const positionClass =\n      launcher.position && positionMap[launcher.position]\n        ? positionMap[launcher.position]\n        : positionMap[\"bottom-right\"];\n\n    const floatingBase =\n      \"persona-fixed persona-flex persona-items-center persona-gap-3 persona-rounded-launcher persona-bg-persona-surface persona-py-2.5 persona-pl-3 persona-pr-3 persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer\";\n    const dockedBase =\n      \"persona-relative persona-mt-4 persona-mb-4 persona-mx-auto persona-flex persona-items-center persona-justify-center persona-rounded-launcher persona-bg-persona-surface persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer\";\n\n    button.className = dockedMode ? dockedBase : `${floatingBase} ${positionClass}`;\n\n    if (!dockedMode) {\n      button.style.zIndex = String(launcher.zIndex ?? DEFAULT_OVERLAY_Z_INDEX);\n    }\n\n    // Apply launcher border and shadow from config (with defaults matching previous Tailwind classes)\n    const defaultBorder = \"1px solid var(--persona-border, #e5e7eb)\";\n    const defaultShadow = \"var(--persona-launcher-shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1))\";\n\n    button.style.border = launcher.border ?? defaultBorder;\n    button.style.boxShadow =\n      launcher.shadow !== undefined\n        ? (launcher.shadow.trim() === \"\" ? \"none\" : launcher.shadow)\n        : defaultShadow;\n\n    if (dockedMode) {\n      // Docked mode uses a 0px column when closed and hides this button; keep no hit target.\n      button.style.width = \"0\";\n      button.style.minWidth = \"0\";\n      button.style.maxWidth = \"0\";\n      button.style.padding = \"0\";\n      button.style.overflow = \"hidden\";\n      button.style.border = \"none\";\n      button.style.boxShadow = \"none\";\n    } else {\n      button.style.width = \"\";\n      button.style.minWidth = \"\";\n      button.style.maxWidth = launcher.collapsedMaxWidth ?? \"\";\n      button.style.justifyContent = \"\";\n      button.style.padding = \"\";\n      button.style.overflow = \"\";\n    }\n  };\n\n  const destroy = () => {\n    button.removeEventListener(\"click\", onToggle);\n    button.remove();\n  };\n\n  // Initial update\n  if (config) {\n    update(config);\n  }\n\n  return {\n    element: button,\n    update,\n    destroy\n  };\n};\n","import { AgentWidgetConfig } from \"../types\";\nimport { AgentWidgetPlugin } from \"../plugins/types\";\nimport { createWrapper, buildPanel, PanelElements } from \"./panel\";\nimport { HeaderElements } from \"./header-builder\";\nimport { createLauncherButton, LauncherButton } from \"./launcher\";\n\n/**\n * Widget view assembly layer.\n *\n * `ui.ts` owns behavior (session dispatch, streaming, scroll, voice, events);\n * this module owns the one-time structural assembly of the widget's DOM and\n * groups the resulting element references into named regions so callers read\n * `view.composer.textarea` instead of destructuring a ~30-field flat object.\n *\n * The grouped refs are a view over the same nodes returned by `buildPanel`;\n * the raw flat `PanelElements` is still exposed as `view.panelElements` for\n * incremental migration. No framework, no virtual DOM: these are the real\n * elements built by the existing `panel.ts` / `composer-*` / `header-*`\n * builders.\n */\n\nexport interface WidgetShellRefs {\n  wrapper: HTMLElement;\n  panel: HTMLElement;\n  /** Composer-bar mode only: viewport-fixed sibling that owns the pill. */\n  pillRoot?: HTMLElement;\n}\n\nexport interface WidgetTranscriptRefs {\n  container: HTMLElement;\n  body: HTMLElement;\n  messagesWrapper: HTMLElement;\n  /** Absolute slot above the composer where interactive sheets mount. */\n  composerOverlay: HTMLElement;\n  introTitle: HTMLElement;\n  introSubtitle: HTMLElement;\n}\n\nexport interface WidgetHeaderRefs {\n  /** The header root element currently mounted in the container. */\n  element: HTMLElement;\n  iconHolder: HTMLElement;\n  headerTitle: HTMLElement;\n  headerSubtitle: HTMLElement;\n  closeButton: HTMLButtonElement;\n  closeButtonWrapper: HTMLElement | null;\n  clearChatButton: HTMLButtonElement | null;\n  clearChatButtonWrapper: HTMLElement | null;\n}\n\nexport interface WidgetComposerRefs {\n  /** The footer root element currently mounted in the panel. */\n  footer: HTMLElement;\n  form: HTMLFormElement;\n  textarea: HTMLTextAreaElement;\n  sendButton: HTMLButtonElement;\n  sendButtonWrapper: HTMLElement;\n  micButton: HTMLButtonElement | null;\n  micButtonWrapper: HTMLElement | null;\n  statusText: HTMLElement;\n  suggestions: HTMLElement;\n  attachmentButton: HTMLButtonElement | null;\n  attachmentButtonWrapper: HTMLElement | null;\n  attachmentInput: HTMLInputElement | null;\n  attachmentPreviewsContainer: HTMLElement | null;\n  actionsRow: HTMLElement;\n  leftActions: HTMLElement;\n  rightActions: HTMLElement;\n  setSendButtonMode: (mode: \"send\" | \"stop\") => void;\n  /** Composer-bar peek banner (undefined for non-composer-bar modes). */\n  peekBanner?: HTMLButtonElement;\n  peekTextNode?: HTMLElement;\n}\n\nexport interface WidgetView {\n  shell: WidgetShellRefs;\n  /** Raw flat refs from `buildPanel`, retained for incremental migration. */\n  panelElements: PanelElements;\n  transcript: WidgetTranscriptRefs;\n  header: WidgetHeaderRefs;\n  composer: WidgetComposerRefs;\n  /**\n   * Swap the mounted header element for `next.header` and mirror the header\n   * sub-refs (icon/title/subtitle/close) onto `view.header`. Used by the\n   * header-layout rebuild path. Returns the new `HeaderElements` for the\n   * caller to mirror into its own locals during incremental migration.\n   */\n  replaceHeader: (next: HeaderElements) => HeaderElements;\n  /**\n   * Swap the mounted footer element for `nextFooter` and update\n   * `view.composer.footer`. The remaining composer sub-refs become stale\n   * after a plugin-provided composer replaces the footer; callers re-derive\n   * them from the new footer (see `bindComposerRefsFromFooter` in ui.ts).\n   */\n  replaceComposer: (nextFooter: HTMLElement) => void;\n}\n\nexport interface CreateWidgetViewOptions {\n  config: AgentWidgetConfig;\n  /** Whether the panel renders a close affordance (panel is toggleable). */\n  showClose: boolean;\n}\n\n/**\n * Build the widget shell + panel once and group the element references into\n * named regions. Equivalent to the previous `createWrapper(config)` +\n * `buildPanel(config, showClose)` pair, plus the grouping/replacement helpers.\n */\nexport const createWidgetView = ({\n  config,\n  showClose,\n}: CreateWidgetViewOptions): WidgetView => {\n  const { wrapper, panel, pillRoot } = createWrapper(config);\n  const panelElements = buildPanel(config, showClose);\n\n  const shell: WidgetShellRefs = { wrapper, panel, pillRoot };\n\n  const transcript: WidgetTranscriptRefs = {\n    container: panelElements.container,\n    body: panelElements.body,\n    messagesWrapper: panelElements.messagesWrapper,\n    composerOverlay: panelElements.composerOverlay,\n    introTitle: panelElements.introTitle,\n    introSubtitle: panelElements.introSubtitle,\n  };\n\n  const header: WidgetHeaderRefs = {\n    element: panelElements.header,\n    iconHolder: panelElements.iconHolder,\n    headerTitle: panelElements.headerTitle,\n    headerSubtitle: panelElements.headerSubtitle,\n    closeButton: panelElements.closeButton,\n    closeButtonWrapper: panelElements.closeButtonWrapper,\n    clearChatButton: panelElements.clearChatButton,\n    clearChatButtonWrapper: panelElements.clearChatButtonWrapper,\n  };\n\n  const composer: WidgetComposerRefs = {\n    footer: panelElements.footer,\n    form: panelElements.composerForm,\n    textarea: panelElements.textarea,\n    sendButton: panelElements.sendButton,\n    sendButtonWrapper: panelElements.sendButtonWrapper,\n    micButton: panelElements.micButton,\n    micButtonWrapper: panelElements.micButtonWrapper,\n    statusText: panelElements.statusText,\n    suggestions: panelElements.suggestions,\n    attachmentButton: panelElements.attachmentButton,\n    attachmentButtonWrapper: panelElements.attachmentButtonWrapper,\n    attachmentInput: panelElements.attachmentInput,\n    attachmentPreviewsContainer: panelElements.attachmentPreviewsContainer,\n    actionsRow: panelElements.actionsRow,\n    leftActions: panelElements.leftActions,\n    rightActions: panelElements.rightActions,\n    setSendButtonMode: panelElements.setSendButtonMode,\n    peekBanner: panelElements.peekBanner,\n    peekTextNode: panelElements.peekTextNode,\n  };\n\n  const replaceHeader = (next: HeaderElements): HeaderElements => {\n    header.element.replaceWith(next.header);\n    header.element = next.header;\n    header.iconHolder = next.iconHolder;\n    header.headerTitle = next.headerTitle;\n    header.headerSubtitle = next.headerSubtitle;\n    header.closeButton = next.closeButton;\n    header.closeButtonWrapper = next.closeButtonWrapper;\n    header.clearChatButton = next.clearChatButton;\n    header.clearChatButtonWrapper = next.clearChatButtonWrapper;\n    return next;\n  };\n\n  const replaceComposer = (nextFooter: HTMLElement): void => {\n    composer.footer.replaceWith(nextFooter);\n    composer.footer = nextFooter;\n  };\n\n  return {\n    shell,\n    panelElements,\n    transcript,\n    header,\n    composer,\n    replaceHeader,\n    replaceComposer,\n  };\n};\n\nexport interface ResolveLauncherOptions {\n  config: AgentWidgetConfig;\n  plugins: AgentWidgetPlugin[];\n  /** Toggles the panel open/closed; wired to the launcher's click. */\n  onToggle: () => void;\n}\n\nexport interface ResolvedLauncher {\n  /**\n   * The default launcher controller (with `update`/`destroy`), or null when a\n   * plugin supplied a custom launcher element.\n   */\n  instance: LauncherButton | null;\n  /** The element to mount: either the default button or the plugin's element. */\n  element: HTMLElement;\n}\n\n/**\n * Resolve the launcher element, honoring a `renderLauncher` plugin if present.\n * De-duplicates the identical default-or-plugin logic that previously lived at\n * both the initial-build and re-enable call sites in ui.ts.\n *\n * When a plugin returns a custom element, `instance` is null (the plugin owns\n * its own updates); otherwise the default `LauncherButton` controller is\n * returned so the caller can drive `update()`/`destroy()`.\n */\nexport const resolveLauncher = ({\n  config,\n  plugins,\n  onToggle,\n}: ResolveLauncherOptions): ResolvedLauncher => {\n  const launcherPlugin = plugins.find((p) => p.renderLauncher);\n  if (launcherPlugin?.renderLauncher) {\n    const customLauncher = launcherPlugin.renderLauncher({\n      config,\n      defaultRenderer: () => createLauncherButton(config, onToggle).element,\n      onToggle,\n    });\n    if (customLauncher) {\n      return { instance: null, element: customLauncher };\n    }\n  }\n\n  const instance = createLauncherButton(config, onToggle);\n  return { instance, element: instance.element };\n};\n","import { createElement } from \"../utils/dom\";\nimport {\n  AgentWidgetMessage,\n  AgentWidgetMessageLayoutConfig,\n  AgentWidgetAvatarConfig,\n  AgentWidgetTimestampConfig,\n  AgentWidgetMessageActionsConfig,\n  AgentWidgetMessageFeedback,\n  LoadingIndicatorRenderContext,\n  ImageContentPart,\n  AudioContentPart,\n  VideoContentPart,\n  FileContentPart,\n  StopReasonKind\n} from \"../types\";\nimport { createIconButton } from \"../utils/buttons\";\nimport { IMAGE_ONLY_MESSAGE_FALLBACK_TEXT } from \"../utils/content\";\nimport {\n  applyStreamBuffer,\n  createSkeletonPlaceholder,\n  createStreamCaret,\n  resolveStreamAnimation,\n  resolveStreamAnimationPlugin,\n  wrapStreamAnimation,\n} from \"../utils/stream-animation\";\n\n/**\n * Default copy for the inline notice rendered when a turn ends with a\n * non-natural stop reason. Deployers override per-reason via\n * `config.copy.stopReasonNotice`. Returns `null` for natural completions\n * (`end_turn`) and uninformative reasons (`unknown`): those never render\n * an affordance.\n */\nexport const getDefaultStopReasonNoticeCopy = (\n  stopReason: StopReasonKind\n): string | null => {\n  switch (stopReason) {\n    case \"max_tool_calls\":\n      return \"Stopped after calling a tool. Send a follow-up to continue.\";\n    case \"length\":\n      return \"Response cut off as max tokens reached. Ask for more to continue.\";\n    case \"content_filter\":\n      return \"The provider filtered this response.\";\n    case \"error\":\n      return \"Something went wrong generating this response.\";\n    case \"end_turn\":\n    case \"unknown\":\n    default:\n      return null;\n  }\n};\n\n/**\n * Resolve the notice text for a stop reason, applying user overrides on\n * top of the built-in defaults. Returns `null` when the reason does not\n * warrant a notice or when the resolved string is empty (deployers can\n * suppress per-reason by setting an empty override).\n */\nexport const resolveStopReasonNoticeText = (\n  stopReason: StopReasonKind | undefined,\n  overrides?: Partial<Record<StopReasonKind, string>>\n): string | null => {\n  if (!stopReason) return null;\n  const fallback = getDefaultStopReasonNoticeCopy(stopReason);\n  // Reasons without a default (end_turn, unknown) never render: overrides\n  // for those keys are intentionally ignored.\n  if (fallback === null) return null;\n  const override = overrides?.[stopReason];\n  const text = override !== undefined ? override : fallback;\n  if (!text) return null;\n  return text;\n};\n\n/**\n * Build the inline notice element rendered on assistant bubbles whose\n * turn ended with `max_tool_calls`, `length`, `content_filter`, or `error`.\n */\nconst createStopReasonNotice = (\n  stopReason: StopReasonKind,\n  text: string\n): HTMLElement => {\n  const notice = createElement(\n    \"div\",\n    \"persona-message-stop-reason persona-text-xs persona-mt-2 persona-italic\"\n  );\n  notice.setAttribute(\"data-stop-reason\", stopReason);\n  notice.setAttribute(\"role\", \"note\");\n  notice.style.opacity = \"0.75\";\n  notice.textContent = text;\n  return notice;\n};\n\n/** Validate that an image src URL uses a safe scheme (blocks javascript: and SVG data URIs). */\nexport const isSafeImageSrc = (src: string): boolean => {\n  const lower = src.toLowerCase();\n  if (lower.startsWith(\"data:image/svg+xml\")) return false;\n  if (/^(?:https?|blob):/i.test(src)) return true;\n  if (lower.startsWith(\"data:image/\")) return true;\n  // Relative URLs are safe\n  if (!src.includes(\":\")) return true;\n  return false;\n};\n\n/**\n * Validate that a media src URL (audio/video/file) uses a safe scheme.\n * Allows http(s), blob:, and inert data: URIs. Blocks `javascript:` and the\n * executable data: types whose payloads a browser would render or run when a\n * user right-clicks the link → \"Open in new tab\" (bypassing `download`):\n * `data:text/html`, `data:text/javascript`, `data:text/xml`,\n * `data:application/xhtml`, and `data:image/svg+xml`. Inert text payloads\n * (`text/plain`, `text/csv`, `text/markdown`) remain allowed so attachments\n * with those mime types render as download links instead of vanishing.\n */\nexport const isSafeMediaSrc = (src: string): boolean => {\n  const lower = src.toLowerCase();\n  if (lower.startsWith(\"javascript:\")) return false;\n  if (lower.startsWith(\"data:text/html\")) return false;\n  if (lower.startsWith(\"data:text/javascript\")) return false;\n  if (lower.startsWith(\"data:text/xml\")) return false;\n  if (lower.startsWith(\"data:application/xhtml\")) return false;\n  if (lower.startsWith(\"data:image/svg+xml\")) return false;\n  if (/^(?:https?|blob):/i.test(src)) return true;\n  if (lower.startsWith(\"data:\")) return true;\n  // Relative URLs are safe\n  if (!src.includes(\":\")) return true;\n  return false;\n};\n\nexport type LoadingIndicatorRenderer = (context: LoadingIndicatorRenderContext) => HTMLElement | null;\n\nexport type MessageTransform = (context: {\n  text: string;\n  message: AgentWidgetMessage;\n  streaming: boolean;\n  raw?: string;\n}) => string;\n\nexport type MessageActionCallbacks = {\n  onCopy?: (message: AgentWidgetMessage) => void;\n  onFeedback?: (feedback: AgentWidgetMessageFeedback) => void;\n};\n\nconst MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX = 320;\nconst MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX = 320;\n\nconst getMessageImageParts = (message: AgentWidgetMessage): ImageContentPart[] => {\n  if (!message.contentParts || message.contentParts.length === 0) {\n    return [];\n  }\n\n  return message.contentParts.filter(\n    (part): part is ImageContentPart =>\n      part.type === \"image\" &&\n      typeof part.image === \"string\" &&\n      part.image.trim().length > 0\n  );\n};\n\nconst getMessageAudioParts = (message: AgentWidgetMessage): AudioContentPart[] => {\n  if (!message.contentParts || message.contentParts.length === 0) return [];\n  return message.contentParts.filter(\n    (part): part is AudioContentPart =>\n      part.type === \"audio\" &&\n      typeof part.audio === \"string\" &&\n      part.audio.trim().length > 0\n  );\n};\n\nconst getMessageVideoParts = (message: AgentWidgetMessage): VideoContentPart[] => {\n  if (!message.contentParts || message.contentParts.length === 0) return [];\n  return message.contentParts.filter(\n    (part): part is VideoContentPart =>\n      part.type === \"video\" &&\n      typeof part.video === \"string\" &&\n      part.video.trim().length > 0\n  );\n};\n\nconst getMessageFileParts = (message: AgentWidgetMessage): FileContentPart[] => {\n  if (!message.contentParts || message.contentParts.length === 0) return [];\n  return message.contentParts.filter(\n    (part): part is FileContentPart =>\n      part.type === \"file\" &&\n      typeof part.data === \"string\" &&\n      part.data.trim().length > 0\n  );\n};\n\nconst createMessageImagePreviews = (\n  imageParts: ImageContentPart[],\n  hasVisibleText: boolean,\n  onPreviewFailed?: () => void\n): HTMLElement | null => {\n  if (imageParts.length === 0) return null;\n\n  try {\n    const container = createElement(\n      \"div\",\n      \"persona-flex persona-flex-col persona-gap-2\"\n    );\n    container.setAttribute(\"data-message-attachments\", \"images\");\n    if (hasVisibleText) {\n      container.style.marginBottom = \"8px\";\n    }\n\n    let visiblePreviewCount = 0;\n    let failureHandled = false;\n\n    const handleTotalPreviewFailure = () => {\n      if (failureHandled) return;\n      failureHandled = true;\n      container.remove();\n      onPreviewFailed?.();\n    };\n\n    imageParts.forEach((imagePart, index) => {\n      const imageElement = createElement(\"img\") as HTMLImageElement;\n      imageElement.alt = imagePart.alt?.trim() || `Attached image ${index + 1}`;\n      imageElement.loading = \"lazy\";\n      imageElement.decoding = \"async\";\n      imageElement.referrerPolicy = \"no-referrer\";\n      imageElement.style.display = \"block\";\n      imageElement.style.width = \"100%\";\n      imageElement.style.maxWidth = `${MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX}px`;\n      imageElement.style.maxHeight = `${MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX}px`;\n      imageElement.style.height = \"auto\";\n      imageElement.style.objectFit = \"contain\";\n      imageElement.style.borderRadius = \"10px\";\n      imageElement.style.backgroundColor = \"var(--persona-attachment-image-bg, var(--persona-container, #f3f4f6))\";\n      imageElement.style.border = \"1px solid var(--persona-attachment-image-border, var(--persona-border, #e5e7eb))\";\n\n      let settled = false;\n      visiblePreviewCount += 1;\n      imageElement.addEventListener(\"error\", () => {\n        if (settled) return;\n        settled = true;\n        visiblePreviewCount = Math.max(0, visiblePreviewCount - 1);\n        imageElement.remove();\n        if (visiblePreviewCount === 0) {\n          handleTotalPreviewFailure();\n        }\n      });\n      imageElement.addEventListener(\"load\", () => {\n        settled = true;\n      });\n\n      if (isSafeImageSrc(imagePart.image)) {\n        imageElement.src = imagePart.image;\n        container.appendChild(imageElement);\n      } else {\n        // Treat blocked images like load errors so fallback triggers correctly\n        settled = true;\n        visiblePreviewCount = Math.max(0, visiblePreviewCount - 1);\n        imageElement.remove();\n      }\n    });\n\n    if (visiblePreviewCount === 0) {\n      handleTotalPreviewFailure();\n      return null;\n    }\n\n    return container;\n  } catch {\n    onPreviewFailed?.();\n    return null;\n  }\n};\n\nconst createMessageAudioPreviews = (\n  audioParts: AudioContentPart[]\n): HTMLElement | null => {\n  if (audioParts.length === 0) return null;\n  try {\n    const container = createElement(\n      \"div\",\n      \"persona-flex persona-flex-col persona-gap-2\"\n    );\n    container.setAttribute(\"data-message-attachments\", \"audio\");\n    let visible = 0;\n    audioParts.forEach((part) => {\n      if (!isSafeMediaSrc(part.audio)) return;\n      const audioElement = createElement(\"audio\") as HTMLAudioElement;\n      audioElement.controls = true;\n      audioElement.preload = \"metadata\";\n      audioElement.src = part.audio;\n      audioElement.style.display = \"block\";\n      audioElement.style.width = \"100%\";\n      audioElement.style.maxWidth = `${MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX}px`;\n      container.appendChild(audioElement);\n      visible += 1;\n    });\n    if (visible === 0) {\n      container.remove();\n      return null;\n    }\n    return container;\n  } catch {\n    return null;\n  }\n};\n\nconst createMessageVideoPreviews = (\n  videoParts: VideoContentPart[]\n): HTMLElement | null => {\n  if (videoParts.length === 0) return null;\n  try {\n    const container = createElement(\n      \"div\",\n      \"persona-flex persona-flex-col persona-gap-2\"\n    );\n    container.setAttribute(\"data-message-attachments\", \"video\");\n    let visible = 0;\n    videoParts.forEach((part) => {\n      if (!isSafeMediaSrc(part.video)) return;\n      const videoElement = createElement(\"video\") as HTMLVideoElement;\n      videoElement.controls = true;\n      videoElement.preload = \"metadata\";\n      videoElement.src = part.video;\n      videoElement.style.display = \"block\";\n      videoElement.style.width = \"100%\";\n      videoElement.style.maxWidth = `${MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX}px`;\n      videoElement.style.maxHeight = `${MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX}px`;\n      videoElement.style.borderRadius = \"10px\";\n      videoElement.style.backgroundColor =\n        \"var(--persona-attachment-image-bg, var(--persona-container, #f3f4f6))\";\n      container.appendChild(videoElement);\n      visible += 1;\n    });\n    if (visible === 0) {\n      container.remove();\n      return null;\n    }\n    return container;\n  } catch {\n    return null;\n  }\n};\n\nconst createMessageFilePreviews = (\n  fileParts: FileContentPart[]\n): HTMLElement | null => {\n  if (fileParts.length === 0) return null;\n  try {\n    const container = createElement(\n      \"div\",\n      \"persona-flex persona-flex-col persona-gap-2\"\n    );\n    container.setAttribute(\"data-message-attachments\", \"files\");\n    let visible = 0;\n    fileParts.forEach((part) => {\n      if (!isSafeMediaSrc(part.data)) return;\n      const link = createElement(\"a\") as HTMLAnchorElement;\n      link.href = part.data;\n      link.download = part.filename;\n      // Cross-origin URLs ignore the `download` attribute, so without\n      // `target=_blank` the link would navigate the chat page to the file.\n      // Pair with `rel=\"noopener noreferrer\"` to prevent reverse tabnabbing.\n      link.target = \"_blank\";\n      link.rel = \"noopener noreferrer\";\n      link.textContent = part.filename;\n      link.className = \"persona-message-file-attachment\";\n      link.style.display = \"inline-flex\";\n      link.style.alignItems = \"center\";\n      link.style.gap = \"6px\";\n      link.style.padding = \"6px 10px\";\n      link.style.borderRadius = \"8px\";\n      link.style.fontSize = \"0.875rem\";\n      link.style.textDecoration = \"underline\";\n      link.style.backgroundColor =\n        \"var(--persona-attachment-file-bg, var(--persona-container, #f3f4f6))\";\n      link.style.border =\n        \"1px solid var(--persona-attachment-file-border, var(--persona-border, #e5e7eb))\";\n      link.style.color = \"inherit\";\n      container.appendChild(link);\n      visible += 1;\n    });\n    if (visible === 0) {\n      container.remove();\n      return null;\n    }\n    return container;\n  } catch {\n    return null;\n  }\n};\n\n// Create typing indicator element\nexport const createTypingIndicator = (): HTMLElement => {\n  const container = document.createElement(\"div\");\n  container.className = \"persona-flex persona-items-center persona-space-x-1 persona-h-5 persona-mt-2\";\n\n  const dot1 = document.createElement(\"div\");\n  dot1.className = \"persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5\";\n  dot1.style.backgroundColor = \"currentColor\";\n  dot1.style.opacity = \"0.4\";\n  dot1.style.animationDelay = \"0ms\";\n\n  const dot2 = document.createElement(\"div\");\n  dot2.className = \"persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5\";\n  dot2.style.backgroundColor = \"currentColor\";\n  dot2.style.opacity = \"0.4\";\n  dot2.style.animationDelay = \"250ms\";\n\n  const dot3 = document.createElement(\"div\");\n  dot3.className = \"persona-animate-typing persona-rounded-full persona-h-1.5 persona-w-1.5\";\n  dot3.style.backgroundColor = \"currentColor\";\n  dot3.style.opacity = \"0.4\";\n  dot3.style.animationDelay = \"500ms\";\n\n  const srOnly = document.createElement(\"span\");\n  srOnly.className = \"persona-sr-only\";\n  srOnly.textContent = \"Loading\";\n\n  container.appendChild(dot1);\n  container.appendChild(dot2);\n  container.appendChild(dot3);\n  container.appendChild(srOnly);\n\n  return container;\n};\n\n/**\n * Render loading indicator with fallback chain:\n * 1. Custom renderer (if provided and returns non-null)\n * 2. Default typing indicator\n */\nexport const renderLoadingIndicatorWithFallback = (\n  location: 'inline' | 'standalone',\n  customRenderer?: LoadingIndicatorRenderer,\n  widgetConfig?: import(\"../types\").AgentWidgetConfig\n): HTMLElement | null => {\n  const context: LoadingIndicatorRenderContext = {\n    config: widgetConfig ?? ({} as import(\"../types\").AgentWidgetConfig),\n    streaming: true,\n    location,\n    defaultRenderer: createTypingIndicator\n  };\n\n  // Try custom renderer first\n  if (customRenderer) {\n    const result = customRenderer(context);\n    if (result !== null) {\n      return result;\n    }\n  }\n\n  // Fall back to default\n  return createTypingIndicator();\n};\n\n/**\n * Create an avatar element\n */\nconst createAvatar = (\n  avatarConfig: AgentWidgetAvatarConfig,\n  role: \"user\" | \"assistant\"\n): HTMLElement => {\n  const avatar = createElement(\n    \"div\",\n    \"persona-flex-shrink-0 persona-w-8 persona-h-8 persona-rounded-full persona-flex persona-items-center persona-justify-center persona-text-sm\"\n  );\n\n  const avatarContent = role === \"user\" \n    ? avatarConfig.userAvatar \n    : avatarConfig.assistantAvatar;\n\n  if (avatarContent) {\n    // Check if it's a URL or emoji/text\n    if (avatarContent.startsWith(\"http\") || avatarContent.startsWith(\"/\") || avatarContent.startsWith(\"data:\")) {\n      const img = createElement(\"img\") as HTMLImageElement;\n      img.src = avatarContent;\n      img.alt = role === \"user\" ? \"User\" : \"Assistant\";\n      img.className = \"persona-w-full persona-h-full persona-rounded-full persona-object-cover\";\n      avatar.appendChild(img);\n    } else {\n      // Emoji or text\n      avatar.textContent = avatarContent;\n      avatar.classList.add(\n        role === \"user\" ? \"persona-bg-persona-accent\" : \"persona-bg-persona-primary\",\n        \"persona-text-white\"\n      );\n    }\n  } else {\n    // Default avatar\n    avatar.textContent = role === \"user\" ? \"U\" : \"A\";\n    avatar.classList.add(\n      role === \"user\" ? \"persona-bg-persona-accent\" : \"persona-bg-persona-primary\",\n      \"persona-text-white\"\n    );\n  }\n\n  return avatar;\n};\n\n/**\n * Create a timestamp element\n */\nconst createTimestamp = (\n  message: AgentWidgetMessage,\n  timestampConfig: AgentWidgetTimestampConfig,\n  tagName: \"div\" | \"span\" = \"div\"\n): HTMLElement => {\n  const timestamp = createElement(\n    tagName,\n    \"persona-text-xs persona-text-persona-muted\"\n  );\n\n  const date = new Date(message.createdAt);\n  \n  if (timestampConfig.format) {\n    timestamp.textContent = timestampConfig.format(date);\n  } else {\n    // Default format: HH:MM\n    timestamp.textContent = date.toLocaleTimeString([], { \n      hour: \"2-digit\", \n      minute: \"2-digit\" \n    });\n  }\n\n  return timestamp;\n};\n\n/**\n * Get bubble classes based on layout preset\n */\nconst getBubbleClasses = (\n  role: \"user\" | \"assistant\" | \"system\",\n  layout: AgentWidgetMessageLayoutConfig[\"layout\"] = \"bubble\"\n): string[] => {\n  const baseClasses = [\"persona-message-bubble\", \"persona-max-w-[85%]\"];\n\n  switch (layout) {\n    case \"flat\":\n      // Flat layout: no bubble styling, just text\n      if (role === \"user\") {\n        baseClasses.push(\n          \"persona-message-user-bubble\",\n          \"persona-ml-auto\",\n          \"persona-text-persona-primary\",\n          \"persona-py-2\"\n        );\n      } else {\n        baseClasses.push(\n          \"persona-message-assistant-bubble\",\n          \"persona-text-persona-primary\",\n          \"persona-py-2\"\n        );\n      }\n      break;\n\n    case \"minimal\":\n      // Minimal layout: reduced padding and styling\n      baseClasses.push(\n        \"persona-text-sm\",\n        \"persona-leading-relaxed\"\n      );\n      if (role === \"user\") {\n        baseClasses.push(\n          \"persona-message-user-bubble\",\n          \"persona-ml-auto\",\n          \"persona-bg-persona-accent\",\n          \"persona-text-white\",\n          \"persona-px-3\",\n          \"persona-py-2\",\n          \"persona-rounded-lg\"\n        );\n      } else {\n        baseClasses.push(\n          \"persona-message-assistant-bubble\",\n          \"persona-bg-persona-surface\",\n          \"persona-text-persona-primary\",\n          \"persona-px-3\",\n          \"persona-py-2\",\n          \"persona-rounded-lg\"\n        );\n      }\n      break;\n\n    case \"bubble\":\n    default:\n      // Default bubble layout\n      baseClasses.push(\n        \"persona-rounded-2xl\",\n        \"persona-text-sm\",\n        \"persona-leading-relaxed\",\n        \"persona-shadow-sm\"\n      );\n      if (role === \"user\") {\n        baseClasses.push(\n          \"persona-message-user-bubble\",\n          \"persona-ml-auto\",\n          \"persona-bg-persona-accent\",\n          \"persona-text-white\",\n          \"persona-px-5\",\n          \"persona-py-3\"\n        );\n      } else {\n        baseClasses.push(\n          \"persona-message-assistant-bubble\",\n          \"persona-bg-persona-surface\",\n          \"persona-border\",\n          \"persona-border-persona-message-border\",\n          \"persona-text-persona-primary\",\n          \"persona-px-5\",\n          \"persona-py-3\"\n        );\n      }\n      break;\n  }\n\n  return baseClasses;\n};\n\n/**\n * Create message action buttons (copy, upvote, downvote)\n *\n * This is a pure rendering function. It creates button elements with the\n * correct `data-action` attributes, icons, and CSS classes. All click\n * handling, vote state management, clipboard logic, and callback dispatch\n * is handled via event delegation in `ui.ts` so that handlers survive\n * idiomorph DOM morphing.\n */\nexport const createMessageActions = (\n  message: AgentWidgetMessage,\n  actionsConfig: AgentWidgetMessageActionsConfig,\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  _callbacks?: MessageActionCallbacks\n): HTMLElement => {\n  const showCopy = actionsConfig.showCopy ?? true;\n  const showUpvote = actionsConfig.showUpvote ?? true;\n  const showDownvote = actionsConfig.showDownvote ?? true;\n  const showReadAloud = actionsConfig.showReadAloud ?? false;\n\n  // Don't render the container at all when no actions are visible\n  if (!showCopy && !showUpvote && !showDownvote && !showReadAloud) {\n    const empty = createElement(\"div\");\n    empty.style.display = \"none\";\n    empty.id = `actions-${message.id}`;\n    empty.setAttribute(\"data-actions-for\", message.id);\n    return empty;\n  }\n\n  const visibility = actionsConfig.visibility ?? \"hover\";\n  const align = actionsConfig.align ?? \"right\";\n  const layout = actionsConfig.layout ?? \"pill-inside\";\n\n  // Map alignment to CSS class\n  const alignClass = {\n    left: \"persona-message-actions-left\",\n    center: \"persona-message-actions-center\",\n    right: \"persona-message-actions-right\",\n  }[align];\n\n  // Map layout to CSS class\n  const layoutClass = {\n    \"pill-inside\": \"persona-message-actions-pill\",\n    \"row-inside\": \"persona-message-actions-row\",\n  }[layout];\n\n  const container = createElement(\n    \"div\",\n    `persona-message-actions persona-flex persona-items-center persona-gap-1 persona-mt-2 ${alignClass} ${layoutClass} ${\n      visibility === \"hover\" ? \"persona-message-actions-hover\" : \"\"\n    }`\n  );\n  // Set id for idiomorph matching (prevents recreation on morph)\n  container.id = `actions-${message.id}`;\n  container.setAttribute(\"data-actions-for\", message.id);\n\n  const createActionButton = (\n    iconName: string,\n    label: string,\n    dataAction: string\n  ): HTMLButtonElement => {\n    const button = createIconButton({\n      icon: iconName,\n      label,\n      size: 14,\n      className: \"persona-message-action-btn\",\n    });\n    button.setAttribute(\"data-action\", dataAction);\n    return button;\n  };\n\n  // Copy button\n  if (showCopy) {\n    container.appendChild(createActionButton(\"copy\", \"Copy message\", \"copy\"));\n  }\n\n  // Read-aloud button. Click handling and play/pause/resume state are managed\n  // in ui.ts (event delegation + ReadAloudController) so they survive morphs.\n  if (showReadAloud) {\n    container.appendChild(createActionButton(\"volume-2\", \"Read aloud\", \"read-aloud\"));\n  }\n\n  // Upvote button\n  if (showUpvote) {\n    container.appendChild(createActionButton(\"thumbs-up\", \"Upvote\", \"upvote\"));\n  }\n\n  // Downvote button\n  if (showDownvote) {\n    container.appendChild(createActionButton(\"thumbs-down\", \"Downvote\", \"downvote\"));\n  }\n\n  return container;\n};\n\n/**\n * Options for creating a standard message bubble\n */\nexport type CreateStandardBubbleOptions = {\n  /**\n   * Custom loading indicator renderer for inline location\n   */\n  loadingIndicatorRenderer?: LoadingIndicatorRenderer;\n  /**\n   * Full widget config (needed for loading indicator context)\n   */\n  widgetConfig?: import(\"../types\").AgentWidgetConfig;\n};\n\n/**\n * Create standard message bubble\n * Supports layout configuration for avatars, timestamps, and visual presets\n */\nexport const createStandardBubble = (\n  message: AgentWidgetMessage,\n  transform: MessageTransform,\n  layoutConfig?: AgentWidgetMessageLayoutConfig,\n  actionsConfig?: AgentWidgetMessageActionsConfig,\n  actionCallbacks?: MessageActionCallbacks,\n  options?: CreateStandardBubbleOptions\n): HTMLElement => {\n  const config = layoutConfig ?? {};\n  const layout = config.layout ?? \"bubble\";\n  const avatarConfig = config.avatar;\n  const timestampConfig = config.timestamp;\n  const showAvatar = avatarConfig?.show ?? false;\n  const showTimestamp = timestampConfig?.show ?? false;\n  const avatarPosition = avatarConfig?.position ?? \"left\";\n  const timestampPosition = timestampConfig?.position ?? \"below\";\n\n  // Create the bubble element\n  const classes = getBubbleClasses(message.role, layout);\n  const bubble = createElement(\"div\", classes.join(\" \"));\n  // Set id for idiomorph matching\n  bubble.id = `bubble-${message.id}`;\n  bubble.setAttribute(\"data-message-id\", message.id);\n\n  bubble.setAttribute(\"data-persona-theme-zone\", message.role === \"user\" ? \"user-message\" : \"assistant-message\");\n\n  // Apply component-level color overrides via CSS variables\n  if (message.role === \"user\") {\n    bubble.style.backgroundColor = 'var(--persona-message-user-bg, var(--persona-accent))';\n    bubble.style.color = 'var(--persona-message-user-text, white)';\n  } else if (message.role === \"assistant\") {\n    bubble.style.backgroundColor = 'var(--persona-message-assistant-bg, var(--persona-surface))';\n    bubble.style.color = 'var(--persona-message-assistant-text, var(--persona-text))';\n  }\n\n  const imageParts = getMessageImageParts(message);\n  const messageContentText = message.content?.trim() ?? \"\";\n  const isImageOnlyFallbackMessage =\n    imageParts.length > 0 && messageContentText === IMAGE_ONLY_MESSAGE_FALLBACK_TEXT;\n  const shouldHideTextUntilPreviewFails = isImageOnlyFallbackMessage;\n\n  const streamAnimation = resolveStreamAnimation(\n    options?.widgetConfig?.features?.streamAnimation\n  );\n  const streamPluginOverrides =\n    options?.widgetConfig?.features?.streamAnimation?.plugins;\n  const streamPlugin =\n    message.role === \"assistant\" && streamAnimation.type !== \"none\"\n      ? resolveStreamAnimationPlugin(streamAnimation.type, streamPluginOverrides)\n      : null;\n  // Stay in \"streaming-animated\" mode while the plugin reports in-flight\n  // work for this message: e.g. glyph-cycle's tick loops still walking\n  // through the tail after the last token arrived. Without this, the final\n  // non-animated render rips out the cycling spans mid-animation.\n  const pluginStillAnimating =\n    message.role === \"assistant\" &&\n    streamPlugin?.isAnimating?.(message) === true;\n  const streamAnimationActive =\n    message.role === \"assistant\" &&\n    streamPlugin !== null &&\n    (Boolean(message.streaming) || pluginStillAnimating);\n\n  if (streamAnimationActive && streamPlugin?.bubbleClass) {\n    bubble.classList.add(streamPlugin.bubbleClass);\n  }\n\n  // Add message content\n  const contentDiv = document.createElement(\"div\");\n  contentDiv.classList.add(\"persona-message-content\");\n\n  // While streaming, lock table column widths (see widget.css) so rows append\n  // without per-chunk horizontal reflow. The class is dropped on the final,\n  // non-streaming render, relaxing tables to natural content-fit widths.\n  if (message.streaming) {\n    contentDiv.classList.add(\"persona-content-streaming\");\n  }\n\n  if (streamAnimationActive && streamPlugin) {\n    if (streamPlugin.containerClass) {\n      contentDiv.classList.add(streamPlugin.containerClass);\n    }\n    contentDiv.style.setProperty(\"--persona-stream-step\", `${streamAnimation.speed}ms`);\n    contentDiv.style.setProperty(\"--persona-stream-duration\", `${streamAnimation.duration}ms`);\n  }\n\n  const bufferedContent = streamAnimationActive\n    ? applyStreamBuffer(\n        message.content ?? \"\",\n        streamAnimation.buffer,\n        streamPlugin,\n        message,\n        Boolean(message.streaming)\n      )\n    : (message.content ?? \"\");\n\n  const transformedContent = transform({\n    text: bufferedContent,\n    message,\n    streaming: Boolean(message.streaming),\n    raw: message.rawContent\n  });\n\n  let animatedContent = transformedContent;\n  if (streamAnimationActive && streamPlugin?.wrap === \"char\") {\n    animatedContent = wrapStreamAnimation(transformedContent, \"char\", message.id, {\n      skipTags: streamPlugin.skipTags,\n    });\n  } else if (streamAnimationActive && streamPlugin?.wrap === \"word\") {\n    animatedContent = wrapStreamAnimation(transformedContent, \"word\", message.id, {\n      skipTags: streamPlugin.skipTags,\n    });\n  }\n\n  let textContentDiv: HTMLElement | null = null;\n\n  if (shouldHideTextUntilPreviewFails) {\n    textContentDiv = document.createElement(\"div\");\n    textContentDiv.innerHTML = animatedContent;\n    textContentDiv.style.display = \"none\";\n    contentDiv.appendChild(textContentDiv);\n  } else {\n    contentDiv.innerHTML = animatedContent;\n  }\n\n  if (\n    streamAnimationActive &&\n    streamPlugin?.useCaret &&\n    !shouldHideTextUntilPreviewFails &&\n    messageContentText\n  ) {\n    const caret = createStreamCaret();\n    // Caret must sit on the same line as the final char. Markdown wraps text\n    // in block elements (<p>, <li>, <pre>), so appending to contentDiv would\n    // drop the caret onto a fresh line. Tuck it after the last char/word span,\n    // or fall back to the last block when no spans exist yet.\n    const spans = contentDiv.querySelectorAll(\n      \".persona-stream-char, .persona-stream-word\"\n    );\n    const lastSpan = spans[spans.length - 1];\n    if (lastSpan?.parentNode) {\n      lastSpan.parentNode.insertBefore(caret, lastSpan.nextSibling);\n    } else {\n      const lastChild = contentDiv.lastElementChild;\n      if (lastChild) {\n        lastChild.appendChild(caret);\n      } else {\n        contentDiv.appendChild(caret);\n      }\n    }\n  }\n\n  // Usung <span>, not <div> for timestamp \n  // because a block child of a <p> gets re-parented outside of it on re-render (idiomorph).\n  if (showTimestamp && timestampPosition === \"inline\" && message.createdAt) {\n    const timestamp = createTimestamp(message, timestampConfig!, \"span\");\n    timestamp.classList.add(\"persona-timestamp-inline\");\n    const lastBlock = contentDiv.lastElementChild;\n    if (lastBlock) {\n      lastBlock.appendChild(timestamp);\n    } else {\n      contentDiv.appendChild(timestamp);\n    }\n  }\n\n  if (imageParts.length > 0) {\n    const imagePreviews = createMessageImagePreviews(\n      imageParts,\n      !shouldHideTextUntilPreviewFails && Boolean(messageContentText),\n      () => {\n        if (shouldHideTextUntilPreviewFails && textContentDiv) {\n          textContentDiv.style.display = \"\";\n        }\n      }\n    );\n\n    if (imagePreviews) {\n      bubble.appendChild(imagePreviews);\n    } else if (shouldHideTextUntilPreviewFails && textContentDiv) {\n      textContentDiv.style.display = \"\";\n    }\n  }\n\n  const audioParts = getMessageAudioParts(message);\n  if (audioParts.length > 0) {\n    const audioPreviews = createMessageAudioPreviews(audioParts);\n    if (audioPreviews) {\n      bubble.appendChild(audioPreviews);\n    }\n  }\n\n  const videoParts = getMessageVideoParts(message);\n  if (videoParts.length > 0) {\n    const videoPreviews = createMessageVideoPreviews(videoParts);\n    if (videoPreviews) {\n      bubble.appendChild(videoPreviews);\n    }\n  }\n\n  const fileParts = getMessageFileParts(message);\n  if (fileParts.length > 0) {\n    const filePreviews = createMessageFilePreviews(fileParts);\n    if (filePreviews) {\n      bubble.appendChild(filePreviews);\n    }\n  }\n\n  bubble.appendChild(contentDiv);\n\n  // Add timestamp below if configured\n  if (showTimestamp && timestampPosition === \"below\" && message.createdAt) {\n    const timestamp = createTimestamp(message, timestampConfig!);\n    timestamp.classList.add(\"persona-mt-1\");\n    bubble.appendChild(timestamp);\n  }\n\n  // Resolve the stop-reason notice (if any). Only assistant messages can\n  // carry a stop reason worth surfacing.\n  const stopReasonNoticeText =\n    message.role === \"assistant\"\n      ? resolveStopReasonNoticeText(\n          message.stopReason,\n          options?.widgetConfig?.copy?.stopReasonNotice\n        )\n      : null;\n\n  // Add typing indicator (or skeleton placeholder) for streaming assistant\n  // messages. Check the buffered content: a plugin's `bufferContent` may\n  // hold back the first N chars (e.g. glyph-cycle waits for 50 chars), during\n  // which the bubble would otherwise appear empty.\n  //\n  // When the `\"line\"` buffer strategy is paired with the skeleton placeholder,\n  // the skeleton trails below any already-revealed content to hint that more\n  // lines are on the way. It disappears on stream completion.\n  if (message.streaming && message.role === \"assistant\") {\n    const hasVisibleContent = Boolean(bufferedContent && bufferedContent.trim());\n    const skeletonEnabled = streamAnimation.placeholder === \"skeleton\";\n    const trailSkeleton =\n      skeletonEnabled && streamAnimation.buffer === \"line\" && hasVisibleContent;\n    if (!hasVisibleContent) {\n      if (skeletonEnabled) {\n        bubble.appendChild(createSkeletonPlaceholder());\n      } else {\n        const indicator = renderLoadingIndicatorWithFallback(\n          'inline',\n          options?.loadingIndicatorRenderer,\n          options?.widgetConfig\n        );\n        if (indicator) {\n          bubble.appendChild(indicator);\n        }\n      }\n    } else if (trailSkeleton) {\n      bubble.appendChild(createSkeletonPlaceholder());\n    }\n  }\n\n  // Append the stop-reason notice for non-natural completions. When the\n  // assistant produced no text (the `max_tool_calls` empty-bubble symptom),\n  // hide the empty content div so the notice carries the entire bubble\n  // instead of trailing under a blank space.\n  if (stopReasonNoticeText && message.stopReason && !message.streaming) {\n    if (!messageContentText) {\n      contentDiv.style.display = \"none\";\n    }\n    bubble.appendChild(createStopReasonNotice(message.stopReason, stopReasonNoticeText));\n  }\n\n  // Add message actions for assistant messages (only when not streaming and has content)\n  const shouldShowActions = \n    message.role === \"assistant\" && \n    !message.streaming && \n    message.content && \n    message.content.trim() &&\n    actionsConfig?.enabled !== false;\n\n  if (shouldShowActions && actionsConfig) {\n    const actions = createMessageActions(message, actionsConfig, actionCallbacks);\n    bubble.appendChild(actions);\n  }\n\n  // If no avatar needed, return bubble directly\n  if (!showAvatar || message.role === \"system\") {\n    return bubble;\n  }\n\n  // Create wrapper with avatar\n  const wrapper = createElement(\n    \"div\",\n    `persona-flex persona-gap-2 ${message.role === \"user\" ? \"persona-flex-row-reverse\" : \"\"}`\n  );\n\n  const avatar = createAvatar(avatarConfig!, message.role);\n\n  if (avatarPosition === \"right\" || (avatarPosition === \"left\" && message.role === \"user\")) {\n    wrapper.append(bubble, avatar);\n  } else {\n    wrapper.append(avatar, bubble);\n  }\n\n  // Adjust bubble max-width when avatar is present\n  bubble.classList.remove(\"persona-max-w-[85%]\");\n  bubble.classList.add(\"persona-max-w-[calc(85%-2.5rem)]\");\n\n  return wrapper;\n};\n\n/**\n * Create bubble with custom renderer support\n * Uses custom renderer if provided in layout config, otherwise falls back to standard bubble\n */\nexport const createBubbleWithLayout = (\n  message: AgentWidgetMessage,\n  transform: MessageTransform,\n  layoutConfig?: AgentWidgetMessageLayoutConfig,\n  actionsConfig?: AgentWidgetMessageActionsConfig,\n  actionCallbacks?: MessageActionCallbacks,\n  options?: CreateStandardBubbleOptions\n): HTMLElement => {\n  const config = layoutConfig ?? {};\n\n  // Check for custom renderers\n  if (message.role === \"user\" && config.renderUserMessage) {\n    return config.renderUserMessage({\n      message,\n      config: {} as any, // Will be populated by caller\n      streaming: Boolean(message.streaming)\n    });\n  }\n\n  if (message.role === \"assistant\" && config.renderAssistantMessage) {\n    return config.renderAssistantMessage({\n      message,\n      config: {} as any, // Will be populated by caller\n      streaming: Boolean(message.streaming)\n    });\n  }\n\n  // Fall back to standard bubble\n  return createStandardBubble(message, transform, layoutConfig, actionsConfig, actionCallbacks, options);\n};\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetConfig, AgentWidgetMessage } from \"../types\";\nimport { describeReasonStatus, computeReasoningElapsed, parseFormattedTemplate } from \"../utils/formatting\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { appendCharSpans } from \"../utils/tool-loading-animation\";\n\n// Expansion state per widget instance\nexport const reasoningExpansionState = new Set<string>();\n\nconst appendRenderedValue = (\n  container: HTMLElement,\n  value: HTMLElement | string | null | undefined\n): boolean => {\n  if (value == null) return false;\n  if (typeof value === \"string\") {\n    container.textContent = value;\n    return true;\n  }\n  container.appendChild(value);\n  return true;\n};\n\nconst getReasoningPreviewText = (message: AgentWidgetMessage, maxLines: number): string => {\n  const text = message.reasoning?.chunks.join(\"\").trim() ?? \"\";\n  if (!text) return \"\";\n  return text\n    .split(/\\r?\\n/)\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .slice(0, maxLines)\n    .join(\"\\n\");\n};\n\n// Helper function to update reasoning bubble UI after expansion state changes\nexport const updateReasoningBubbleUI = (messageId: string, bubble: HTMLElement): void => {\n  const expanded = reasoningExpansionState.has(messageId);\n  const header = bubble.querySelector('button[data-expand-header=\"true\"]') as HTMLElement;\n  const content = bubble.querySelector('.persona-border-t') as HTMLElement;\n  const preview = bubble.querySelector('[data-persona-collapsed-preview=\"reasoning\"]') as HTMLElement | null;\n  \n  if (!header || !content) return;\n  \n  header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n  \n  // Find toggle icon container - it's the direct child div of headerMeta (which has persona-ml-auto)\n  const headerMeta = header.querySelector('.persona-ml-auto') as HTMLElement;\n  const toggleIcon = headerMeta?.querySelector(':scope > .persona-flex.persona-items-center') as HTMLElement;\n  if (toggleIcon) {\n    toggleIcon.innerHTML = \"\";\n    const iconColor = \"currentColor\";\n    const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n    if (chevronIcon) {\n      toggleIcon.appendChild(chevronIcon);\n    } else {\n      toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n    }\n  }\n  \n  content.style.display = expanded ? \"\" : \"none\";\n  if (preview) {\n    preview.style.display = expanded\n      ? \"none\"\n      : ((preview.textContent || preview.childNodes.length) ? \"\" : \"none\");\n  }\n};\n\nexport const createReasoningBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {\n  const reasoning = message.reasoning;\n  const bubble = createElement(\n    \"div\",\n    [\n      \"persona-message-bubble\",\n      \"persona-reasoning-bubble\",\n      \"persona-w-full\",\n      \"persona-max-w-[85%]\",\n      \"persona-rounded-2xl\",\n      \"persona-bg-persona-surface\",\n      \"persona-border\",\n      \"persona-border-persona-message-border\",\n      \"persona-text-persona-primary\",\n      \"persona-shadow-sm\",\n      \"persona-overflow-hidden\",\n      \"persona-px-0\",\n      \"persona-py-0\"\n    ].join(\" \")\n  );\n  // Set id for idiomorph matching\n  bubble.id = `bubble-${message.id}`;\n  bubble.setAttribute(\"data-message-id\", message.id);\n\n  if (!reasoning) {\n    return bubble;\n  }\n\n  const reasoningDisplayConfig = config?.features?.reasoningDisplay ?? {};\n  const expandable = reasoningDisplayConfig.expandable !== false;\n  let expanded = expandable && reasoningExpansionState.has(message.id);\n  const isActive = reasoning.status !== \"complete\";\n  const previewText = getReasoningPreviewText(message, reasoningDisplayConfig.previewMaxLines ?? 3);\n  const header = createElement(\n    \"button\",\n    expandable\n      ? \"persona-flex persona-w-full persona-items-center persona-justify-between persona-gap-3 persona-bg-transparent persona-px-4 persona-py-3 persona-text-left persona-cursor-pointer persona-border-none\"\n      : \"persona-flex persona-w-full persona-items-center persona-justify-between persona-gap-3 persona-bg-transparent persona-px-4 persona-py-3 persona-text-left persona-cursor-default persona-border-none\"\n  ) as HTMLButtonElement;\n  header.type = \"button\";\n  if (expandable) {\n    header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n    header.setAttribute(\"data-expand-header\", \"true\");\n  }\n  header.setAttribute(\"data-bubble-type\", \"reasoning\");\n\n  const headerContent = createElement(\"div\", \"persona-flex persona-flex-col persona-text-left\");\n  const title = createElement(\"span\", \"persona-text-xs persona-text-persona-primary\");\n  const defaultSummary = \"Thinking...\";\n  const reasoningConfig = config?.reasoning ?? {};\n\n  // Elapsed helpers: defined early so they're available to renderCollapsedSummary\n  const startedAt = String(reasoning.startedAt ?? Date.now());\n\n  const createElapsedSpan = (): HTMLElement => {\n    const span = createElement(\"span\", \"\");\n    span.setAttribute(\"data-tool-elapsed\", startedAt);\n    span.textContent = computeReasoningElapsed(reasoning);\n    return span;\n  };\n\n  const customSummary = reasoningConfig.renderCollapsedSummary?.({\n    message,\n    reasoning,\n    defaultSummary,\n    previewText,\n    isActive,\n    config: config ?? {},\n    elapsed: computeReasoningElapsed(reasoning),\n    createElapsedElement: createElapsedSpan,\n  });\n  if (typeof customSummary === \"string\" && customSummary.trim()) {\n    title.textContent = customSummary;\n    headerContent.appendChild(title);\n  } else if (customSummary instanceof HTMLElement) {\n    headerContent.appendChild(customSummary);\n  } else {\n    title.textContent = defaultSummary;\n    headerContent.appendChild(title);\n  }\n\n  // Status span: used in the legacy (no-template) path\n  const status = createElement(\"span\", \"persona-text-xs persona-text-persona-primary\");\n  status.textContent = describeReasonStatus(reasoning);\n  headerContent.appendChild(status);\n\n  // Template and animation support\n  const loadingAnimation = reasoningDisplayConfig.loadingAnimation ?? \"none\";\n  const activeTemplate = reasoningConfig.activeTextTemplate;\n  const completeTemplate = reasoningConfig.completeTextTemplate;\n  const currentTemplate = isActive ? activeTemplate : completeTemplate;\n  const skipCustomElement = customSummary instanceof HTMLElement;\n\n  /**\n   * Renders a template into the title element, handling:\n   * - Inline formatting markers: **bold**, *italic*, ~dim~\n   * - {duration} as a live-updating elapsed span (active) or static text (complete)\n   * - Character-by-character animation wrapping when `animated` is true\n   */\n  const renderFormattedTitle = (template: string, animated: boolean) => {\n    title.textContent = \"\";\n    const segments = parseFormattedTemplate(template, \"\");\n    let charIndex = 0;\n\n    for (const seg of segments) {\n      const parent = seg.styles.length > 0\n        ? (() => {\n            const w = createElement(\"span\", seg.styles.map(s => `persona-tool-text-${s}`).join(\" \"));\n            title.appendChild(w);\n            return w;\n          })()\n        : title;\n\n      if (seg.isDuration && isActive) {\n        parent.appendChild(createElapsedSpan());\n      } else {\n        const text = seg.isDuration ? computeReasoningElapsed(reasoning) : seg.text;\n        if (animated) {\n          charIndex = appendCharSpans(parent, text, charIndex);\n        } else {\n          parent.appendChild(document.createTextNode(text));\n        }\n      }\n    }\n  };\n\n  // Apply template + animation, or fall back to legacy title/status approach\n  if (!skipCustomElement && currentTemplate) {\n    // Template mode: unified title replaces separate title/status spans\n    status.style.display = \"none\";\n    title.style.display = \"\";\n\n    if (isActive && loadingAnimation !== \"none\") {\n      const animDuration = reasoningConfig.loadingAnimationDuration ?? 2000;\n      title.setAttribute(\"data-preserve-animation\", \"true\");\n\n      if (loadingAnimation === \"pulse\") {\n        title.classList.add(\"persona-tool-loading-pulse\");\n        title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n        renderFormattedTitle(currentTemplate, false);\n      } else {\n        title.classList.add(`persona-tool-loading-${loadingAnimation}`);\n        title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n\n        if (loadingAnimation === \"shimmer-color\") {\n          if (reasoningConfig.loadingAnimationColor) {\n            title.style.setProperty(\"--persona-tool-anim-color\", reasoningConfig.loadingAnimationColor);\n          }\n          if (reasoningConfig.loadingAnimationSecondaryColor) {\n            title.style.setProperty(\"--persona-tool-anim-secondary-color\", reasoningConfig.loadingAnimationSecondaryColor);\n          }\n        }\n\n        renderFormattedTitle(currentTemplate, true);\n      }\n    } else {\n      renderFormattedTitle(currentTemplate, false);\n    }\n  } else if (!skipCustomElement && isActive && loadingAnimation !== \"none\") {\n    // Animation without template: animate the default \"Thinking...\" text\n    title.style.display = \"\";\n    const animDuration = reasoningConfig.loadingAnimationDuration ?? 2000;\n    title.setAttribute(\"data-preserve-animation\", \"true\");\n\n    if (loadingAnimation === \"pulse\") {\n      title.classList.add(\"persona-tool-loading-pulse\");\n      title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n    } else {\n      title.classList.add(`persona-tool-loading-${loadingAnimation}`);\n      title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n\n      if (loadingAnimation === \"shimmer-color\") {\n        if (reasoningConfig.loadingAnimationColor) {\n          title.style.setProperty(\"--persona-tool-anim-color\", reasoningConfig.loadingAnimationColor);\n        }\n        if (reasoningConfig.loadingAnimationSecondaryColor) {\n          title.style.setProperty(\"--persona-tool-anim-secondary-color\", reasoningConfig.loadingAnimationSecondaryColor);\n        }\n      }\n\n      const text = title.textContent || defaultSummary;\n      title.textContent = \"\";\n      appendCharSpans(title, text, 0);\n    }\n\n    // Legacy: hide title on complete, show status\n    if (reasoning.status === \"complete\") {\n      title.style.display = \"none\";\n    }\n  } else if (!skipCustomElement) {\n    // Legacy path: no template, no animation\n    if (reasoning.status === \"complete\") {\n      title.style.display = \"none\";\n    } else {\n      title.style.display = \"\";\n    }\n  }\n\n  let toggleIcon: HTMLElement | null = null;\n  if (expandable) {\n    toggleIcon = createElement(\"div\", \"persona-flex persona-items-center\");\n    const iconColor = \"currentColor\";\n    const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n    if (chevronIcon) {\n      toggleIcon.appendChild(chevronIcon);\n    } else {\n      toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n    }\n\n    const headerMeta = createElement(\"div\", \"persona-flex persona-items-center persona-ml-auto\");\n    headerMeta.append(toggleIcon);\n    header.append(headerContent, headerMeta);\n  } else {\n    header.append(headerContent);\n  }\n\n  const collapsedPreview = createElement(\n    \"div\",\n    \"persona-px-4 persona-py-3 persona-text-xs persona-leading-snug persona-text-persona-muted\"\n  );\n  collapsedPreview.setAttribute(\"data-persona-collapsed-preview\", \"reasoning\");\n  collapsedPreview.style.display = \"none\";\n  collapsedPreview.style.whiteSpace = \"pre-wrap\";\n\n  if (!expanded && isActive && reasoningDisplayConfig.activePreview && previewText) {\n    const renderedPreview = config?.reasoning?.renderCollapsedPreview?.({\n      message,\n      reasoning,\n      defaultPreview: previewText,\n      isActive,\n      config: config ?? {},\n    });\n    if (!appendRenderedValue(collapsedPreview, renderedPreview)) {\n      collapsedPreview.textContent = previewText;\n    }\n    collapsedPreview.style.display = \"\";\n  }\n\n  if (!expanded && isActive && reasoningDisplayConfig.activeMinHeight) {\n    bubble.style.minHeight = reasoningDisplayConfig.activeMinHeight;\n  }\n\n  if (!expandable) {\n    bubble.append(header, collapsedPreview);\n    return bubble;\n  }\n\n  const content = createElement(\n    \"div\",\n    \"persona-border-t persona-border-gray-200 persona-bg-gray-50 persona-px-4 persona-py-3\"\n  );\n  content.style.display = expanded ? \"\" : \"none\";\n\n  const text = reasoning.chunks.join(\"\");\n  const body = createElement(\n    \"div\",\n    \"persona-whitespace-pre-wrap persona-text-xs persona-leading-snug persona-text-persona-muted\"\n  );\n  body.textContent =\n    text ||\n    (reasoning.status === \"complete\"\n      ? \"No additional context was shared.\"\n      : \"Waiting for details…\");\n  content.appendChild(body);\n\n  const applyExpansionState = () => {\n    header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n    if (toggleIcon) {\n      toggleIcon.innerHTML = \"\";\n      const iconColor = \"currentColor\";\n      const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n      if (chevronIcon) {\n        toggleIcon.appendChild(chevronIcon);\n      } else {\n        toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n      }\n    }\n    content.style.display = expanded ? \"\" : \"none\";\n    collapsedPreview.style.display = expanded ? \"none\" : ((collapsedPreview.textContent || collapsedPreview.childNodes.length) ? \"\" : \"none\");\n  };\n\n  applyExpansionState();\n\n  bubble.append(header, collapsedPreview, content);\n  return bubble;\n};\n\n\n\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetMessage, AgentWidgetConfig } from \"../types\";\nimport { formatUnknownValue, describeToolTitle, resolveToolHeaderText, computeToolElapsed, parseFormattedTemplate } from \"../utils/formatting\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { appendCharSpans } from \"../utils/tool-loading-animation\";\n\n// Expansion state per widget instance\nexport const toolExpansionState = new Set<string>();\n\n\nconst appendRenderedValue = (\n  container: HTMLElement,\n  value: HTMLElement | string | null | undefined\n): boolean => {\n  if (value == null) return false;\n  if (typeof value === \"string\") {\n    container.textContent = value;\n    return true;\n  }\n  container.appendChild(value);\n  return true;\n};\n\nconst getToolPreviewText = (message: AgentWidgetMessage, maxLines: number): string => {\n  const tool = message.toolCall;\n  if (!tool) return \"\";\n\n  const chunkText = (tool.chunks ?? []).join(\"\").trim();\n  if (chunkText) {\n    const lines = chunkText\n      .split(/\\r?\\n/)\n      .map((line) => line.trim())\n      .filter(Boolean)\n      .slice(-maxLines);\n    return lines.join(\"\\n\");\n  }\n\n  const argsText = formatUnknownValue(tool.args).trim();\n  if (!argsText) return \"\";\n\n  return argsText\n    .split(/\\r?\\n/)\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .slice(0, maxLines)\n    .join(\"\\n\");\n};\n\n/**\n * Apply the colors for a tool-bubble code block (Arguments / Activity / Result).\n *\n * Defaults are theme-aware tokens so the blocks stay readable in dark themes.\n */\nconst applyToolCodeBlockColors = (\n  pre: HTMLElement,\n  toolCallConfig: NonNullable<AgentWidgetConfig[\"toolCall\"]>\n): void => {\n  pre.style.backgroundColor =\n    toolCallConfig.codeBlockBackgroundColor ?? \"var(--persona-container, #f3f4f6)\";\n  pre.style.borderColor =\n    toolCallConfig.codeBlockBorderColor ?? \"var(--persona-border, #e5e7eb)\";\n  pre.style.color = toolCallConfig.codeBlockTextColor ?? \"var(--persona-text, #171717)\";\n};\n\nconst getToolSummaryText = (\n  message: AgentWidgetMessage,\n  config?: AgentWidgetConfig\n): { summary: string; previewText: string; isActive: boolean } => {\n  const tool = message.toolCall;\n  const toolDisplayConfig = config?.features?.toolCallDisplay;\n  const collapsedMode = toolDisplayConfig?.collapsedMode ?? \"tool-call\";\n  const previewText = getToolPreviewText(message, toolDisplayConfig?.previewMaxLines ?? 3);\n  const defaultSummary = tool ? describeToolTitle(tool) : \"\";\n\n  if (!tool) {\n    return { summary: defaultSummary, previewText, isActive: false };\n  }\n\n  const isActive = tool.status !== \"complete\";\n  const toolCallConfig = config?.toolCall ?? {};\n  let summary = defaultSummary;\n  if (collapsedMode === \"tool-name\") {\n    summary = tool.name?.trim() || defaultSummary;\n  } else if (collapsedMode === \"tool-preview\" && previewText) {\n    summary = previewText;\n  }\n\n  // Apply text templates if configured\n  if (isActive && toolCallConfig.activeTextTemplate) {\n    summary = resolveToolHeaderText(tool, toolCallConfig.activeTextTemplate, summary);\n  } else if (!isActive && toolCallConfig.completeTextTemplate) {\n    summary = resolveToolHeaderText(tool, toolCallConfig.completeTextTemplate, summary);\n  }\n\n  return { summary, previewText, isActive };\n};\n\n// Helper function to update tool bubble UI after expansion state changes\nexport const updateToolBubbleUI = (messageId: string, bubble: HTMLElement, config?: AgentWidgetConfig): void => {\n  const expanded = toolExpansionState.has(messageId);\n  const toolCallConfig = config?.toolCall ?? {};\n  const header = bubble.querySelector('button[data-expand-header=\"true\"]') as HTMLElement;\n  const content = bubble.querySelector('.persona-border-t') as HTMLElement;\n  const preview = bubble.querySelector('[data-persona-collapsed-preview=\"tool\"]') as HTMLElement | null;\n  \n  if (!header || !content) return;\n  \n  header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n  \n  // Find toggle icon container - it's the direct child div of headerMeta (which has persona-ml-auto)\n  const headerMeta = header.querySelector('.persona-ml-auto') as HTMLElement;\n  const toggleIcon = headerMeta?.querySelector(':scope > .persona-flex.persona-items-center') as HTMLElement;\n  if (toggleIcon) {\n    toggleIcon.innerHTML = \"\";\n    // Default the toggle chevron to the tool-call title color so it stays\n    // readable on whatever surface the title does. The title falls back to\n    // `.persona-text-persona-primary` (var(--persona-primary)) when no\n    // `headerTextColor` is set, so mirror that here instead of `currentColor`.\n    const iconColor =\n      toolCallConfig.toggleTextColor ||\n      toolCallConfig.headerTextColor ||\n      \"var(--persona-primary, #171717)\";\n    const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n    if (chevronIcon) {\n      toggleIcon.appendChild(chevronIcon);\n    } else {\n      toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n    }\n  }\n  \n  content.style.display = expanded ? \"\" : \"none\";\n  if (preview) {\n    preview.style.display = expanded\n      ? \"none\"\n      : ((preview.textContent || preview.childNodes.length) ? \"\" : \"none\");\n  }\n};\n\nexport const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {\n  const tool = message.toolCall;\n  const toolCallConfig = config?.toolCall ?? {};\n  \n  const bubble = createElement(\n    \"div\",\n    [\n      \"persona-message-bubble\",\n      \"persona-tool-bubble\",\n      \"persona-w-full\",\n      \"persona-max-w-[85%]\",\n      \"persona-rounded-2xl\",\n      \"persona-bg-persona-surface\",\n      \"persona-border\",\n      \"persona-border-persona-message-border\",\n      \"persona-text-persona-primary\",\n      \"persona-shadow-sm\",\n      \"persona-overflow-hidden\",\n      \"persona-px-0\",\n      \"persona-py-0\"\n    ].join(\" \")\n  );\n  // Set id for idiomorph matching\n  bubble.id = `bubble-${message.id}`;\n  bubble.setAttribute(\"data-message-id\", message.id);\n\n  // Apply bubble-level styles\n  if (toolCallConfig.backgroundColor) {\n    bubble.style.backgroundColor = toolCallConfig.backgroundColor;\n  }\n  if (toolCallConfig.borderColor) {\n    bubble.style.borderColor = toolCallConfig.borderColor;\n  }\n  if (toolCallConfig.borderWidth) {\n    bubble.style.borderWidth = toolCallConfig.borderWidth;\n  }\n  if (toolCallConfig.borderRadius) {\n    bubble.style.borderRadius = toolCallConfig.borderRadius;\n  }\n  bubble.style.boxShadow =\n    toolCallConfig.shadow !== undefined\n      ? (toolCallConfig.shadow.trim() === \"\" ? \"none\" : toolCallConfig.shadow)\n      : \"var(--persona-tool-bubble-shadow, 0 5px 15px rgba(15, 23, 42, 0.08))\";\n\n  if (!tool) {\n    return bubble;\n  }\n\n  const toolDisplayConfig = config?.features?.toolCallDisplay ?? {};\n  const expandable = toolDisplayConfig.expandable !== false;\n  let expanded = expandable && toolExpansionState.has(message.id);\n  const { summary, previewText, isActive } = getToolSummaryText(message, config);\n\n  const header = createElement(\n    \"button\",\n    expandable\n      ? \"persona-flex persona-w-full persona-items-center persona-justify-between persona-gap-3 persona-bg-transparent persona-px-4 persona-py-3 persona-text-left persona-cursor-pointer persona-border-none\"\n      : \"persona-flex persona-w-full persona-items-center persona-justify-between persona-gap-3 persona-bg-transparent persona-px-4 persona-py-3 persona-text-left persona-cursor-default persona-border-none\"\n  ) as HTMLButtonElement;\n  header.type = \"button\";\n  if (expandable) {\n    header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n    header.setAttribute(\"data-expand-header\", \"true\");\n  }\n  header.setAttribute(\"data-bubble-type\", \"tool\");\n\n  // Apply header styles\n  if (toolCallConfig.headerBackgroundColor) {\n    header.style.backgroundColor = toolCallConfig.headerBackgroundColor;\n  }\n  if (toolCallConfig.headerPaddingX) {\n    header.style.paddingLeft = toolCallConfig.headerPaddingX;\n    header.style.paddingRight = toolCallConfig.headerPaddingX;\n  }\n  if (toolCallConfig.headerPaddingY) {\n    header.style.paddingTop = toolCallConfig.headerPaddingY;\n    header.style.paddingBottom = toolCallConfig.headerPaddingY;\n  }\n\n  const headerContent = createElement(\"div\", \"persona-flex persona-flex-col persona-text-left\");\n  const title = createElement(\"span\", \"persona-text-xs persona-text-persona-primary\");\n  if (toolCallConfig.headerTextColor) {\n    title.style.color = toolCallConfig.headerTextColor;\n  }\n\n  // Elapsed helpers: defined early so they're available to renderCollapsedSummary\n  const startedAt = String(tool.startedAt ?? Date.now());\n\n  // Helper: build a <span data-tool-elapsed> that the global timer in ui.ts updates\n  const createElapsedSpan = (): HTMLElement => {\n    const span = createElement(\"span\", \"\");\n    span.setAttribute(\"data-tool-elapsed\", startedAt);\n    span.textContent = computeToolElapsed(tool);\n    return span;\n  };\n\n  const customSummary = toolCallConfig.renderCollapsedSummary?.({\n    message,\n    toolCall: tool,\n    defaultSummary: summary,\n    previewText,\n    collapsedMode: toolDisplayConfig.collapsedMode ?? \"tool-call\",\n    isActive,\n    config: config ?? {},\n    elapsed: computeToolElapsed(tool),\n    createElapsedElement: createElapsedSpan,\n  });\n  if (typeof customSummary === \"string\" && customSummary.trim()) {\n    title.textContent = customSummary;\n    headerContent.appendChild(title);\n  } else if (customSummary instanceof HTMLElement) {\n    headerContent.appendChild(customSummary);\n  } else {\n    title.textContent = summary;\n    headerContent.appendChild(title);\n  }\n\n  // Apply loading animation when tool is active and no custom HTMLElement was provided\n  const loadingAnimation = toolDisplayConfig.loadingAnimation ?? \"none\";\n  const activeTemplate = toolCallConfig.activeTextTemplate;\n  const completeTemplate = toolCallConfig.completeTextTemplate;\n  const currentTemplate = isActive ? activeTemplate : completeTemplate;\n  const skipCustomElement = customSummary instanceof HTMLElement;\n\n  /**\n   * Renders a template into the title element, handling:\n   * - Inline formatting markers: **bold**, *italic*, ~dim~\n   * - {duration} as a live-updating elapsed span (active) or static text (complete)\n   * - Character-by-character animation wrapping when `animated` is true\n   */\n  const renderFormattedTitle = (template: string, animated: boolean) => {\n    title.textContent = \"\";\n    const toolName = tool.name?.trim() || \"tool\";\n    const segments = parseFormattedTemplate(template, toolName);\n    let charIndex = 0;\n\n    for (const seg of segments) {\n      // Determine parent: wrap in a styled span if formatting is present\n      const parent = seg.styles.length > 0\n        ? (() => {\n            const w = createElement(\"span\", seg.styles.map(s => `persona-tool-text-${s}`).join(\" \"));\n            title.appendChild(w);\n            return w;\n          })()\n        : title;\n\n      if (seg.isDuration && isActive) {\n        // Live-updating elapsed span for active tools\n        parent.appendChild(createElapsedSpan());\n      } else {\n        // Static text (or resolved duration for completed tools)\n        const text = seg.isDuration ? computeToolElapsed(tool) : seg.text;\n        if (animated) {\n          charIndex = appendCharSpans(parent, text, charIndex);\n        } else {\n          parent.appendChild(document.createTextNode(text));\n        }\n      }\n    }\n  };\n\n  if (!skipCustomElement) {\n    if (isActive && loadingAnimation !== \"none\") {\n      const animDuration = toolCallConfig.loadingAnimationDuration ?? 2000;\n      title.setAttribute(\"data-preserve-animation\", \"true\");\n\n      if (loadingAnimation === \"pulse\") {\n        title.classList.add(\"persona-tool-loading-pulse\");\n        title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n        if (currentTemplate) {\n          renderFormattedTitle(currentTemplate, false);\n        }\n      } else {\n        // Character-by-character modes: shimmer, shimmer-color, rainbow\n        title.classList.add(`persona-tool-loading-${loadingAnimation}`);\n        title.style.setProperty(\"--persona-tool-anim-duration\", `${animDuration}ms`);\n\n        if (loadingAnimation === \"shimmer-color\") {\n          if (toolCallConfig.loadingAnimationColor) {\n            title.style.setProperty(\"--persona-tool-anim-color\", toolCallConfig.loadingAnimationColor);\n          }\n          if (toolCallConfig.loadingAnimationSecondaryColor) {\n            title.style.setProperty(\"--persona-tool-anim-secondary-color\", toolCallConfig.loadingAnimationSecondaryColor);\n          }\n        }\n\n        if (currentTemplate) {\n          renderFormattedTitle(currentTemplate, true);\n        } else {\n          const text = title.textContent || summary;\n          title.textContent = \"\";\n          appendCharSpans(title, text, 0);\n        }\n      }\n    } else if (currentTemplate) {\n      // Template with formatting but no animation (or completed tool)\n      renderFormattedTitle(currentTemplate, false);\n    }\n  }\n\n  let toggleIcon: HTMLElement | null = null;\n  if (expandable) {\n    toggleIcon = createElement(\"div\", \"persona-flex persona-items-center\");\n    // Default the toggle chevron to the tool-call title color so it stays\n    // readable on whatever surface the title does. The title falls back to\n    // `.persona-text-persona-primary` (var(--persona-primary)) when no\n    // `headerTextColor` is set, so mirror that here instead of `currentColor`.\n    const iconColor =\n      toolCallConfig.toggleTextColor ||\n      toolCallConfig.headerTextColor ||\n      \"var(--persona-primary, #171717)\";\n    const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n    if (chevronIcon) {\n      toggleIcon.appendChild(chevronIcon);\n    } else {\n      toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n    }\n\n    const headerMeta = createElement(\"div\", \"persona-flex persona-items-center persona-gap-2 persona-ml-auto\");\n    headerMeta.append(toggleIcon);\n    header.append(headerContent, headerMeta);\n  } else {\n    header.append(headerContent);\n  }\n\n  const collapsedPreview = createElement(\n    \"div\",\n    \"persona-px-4 persona-py-3 persona-text-xs persona-leading-snug persona-text-persona-muted\"\n  );\n  collapsedPreview.setAttribute(\"data-persona-collapsed-preview\", \"tool\");\n  collapsedPreview.style.display = \"none\";\n  collapsedPreview.style.whiteSpace = \"pre-wrap\";\n\n  if (\n    !expanded &&\n    isActive &&\n    toolDisplayConfig.activePreview &&\n    previewText\n  ) {\n    const renderedPreview = toolCallConfig.renderCollapsedPreview?.({\n      message,\n      toolCall: tool,\n      defaultPreview: previewText,\n      isActive,\n      config: config ?? {},\n    });\n    if (!appendRenderedValue(collapsedPreview, renderedPreview)) {\n      collapsedPreview.textContent = previewText;\n    }\n    collapsedPreview.style.display = \"\";\n  }\n\n  if (!expanded && isActive && toolDisplayConfig.activeMinHeight) {\n    bubble.style.minHeight = toolDisplayConfig.activeMinHeight;\n  }\n\n  if (!expandable) {\n    bubble.append(header, collapsedPreview);\n    return bubble;\n  }\n\n  const content = createElement(\n    \"div\",\n    \"persona-border-t persona-border-gray-200 persona-bg-gray-50 persona-space-y-3 persona-px-4 persona-py-3\"\n  );\n  content.style.display = expanded ? \"\" : \"none\";\n\n  // Apply content styles\n  if (toolCallConfig.contentBackgroundColor) {\n    content.style.backgroundColor = toolCallConfig.contentBackgroundColor;\n  }\n  if (toolCallConfig.contentTextColor) {\n    content.style.color = toolCallConfig.contentTextColor;\n  }\n  if (toolCallConfig.contentPaddingX) {\n    content.style.paddingLeft = toolCallConfig.contentPaddingX;\n    content.style.paddingRight = toolCallConfig.contentPaddingX;\n  }\n  if (toolCallConfig.contentPaddingY) {\n    content.style.paddingTop = toolCallConfig.contentPaddingY;\n    content.style.paddingBottom = toolCallConfig.contentPaddingY;\n  }\n\n  // Add tool name at the top of content\n  if (tool.name) {\n    const toolName = createElement(\"div\", \"persona-text-xs persona-text-persona-muted persona-italic\");\n    if (toolCallConfig.contentTextColor) {\n      toolName.style.color = toolCallConfig.contentTextColor;\n    } else if (toolCallConfig.headerTextColor) {\n      toolName.style.color = toolCallConfig.headerTextColor;\n    }\n    toolName.textContent = tool.name;\n    content.appendChild(toolName);\n  }\n\n  if (tool.args !== undefined) {\n    const argsBlock = createElement(\"div\", \"persona-space-y-1\");\n    const argsLabel = createElement(\n      \"div\",\n      \"persona-text-xs persona-text-persona-muted\"\n    );\n    if (toolCallConfig.labelTextColor) {\n      argsLabel.style.color = toolCallConfig.labelTextColor;\n    }\n    argsLabel.textContent = \"Arguments\";\n    const argsPre = createElement(\n      \"pre\",\n      \"persona-max-h-48 persona-overflow-auto persona-whitespace-pre-wrap persona-rounded-lg persona-border persona-px-3 persona-py-2 persona-text-xs\"\n    );\n    // Ensure font size matches header text (0.75rem / 12px)\n    argsPre.style.fontSize = \"0.75rem\";\n    argsPre.style.lineHeight = \"1rem\";\n    applyToolCodeBlockColors(argsPre, toolCallConfig);\n    argsPre.textContent = formatUnknownValue(tool.args);\n    argsBlock.append(argsLabel, argsPre);\n    content.appendChild(argsBlock);\n  }\n\n  if (tool.chunks && tool.chunks.length) {\n    const logsBlock = createElement(\"div\", \"persona-space-y-1\");\n    const logsLabel = createElement(\n      \"div\",\n      \"persona-text-xs persona-text-persona-muted\"\n    );\n    if (toolCallConfig.labelTextColor) {\n      logsLabel.style.color = toolCallConfig.labelTextColor;\n    }\n    logsLabel.textContent = \"Activity\";\n    const logsPre = createElement(\n      \"pre\",\n      \"persona-max-h-48 persona-overflow-auto persona-whitespace-pre-wrap persona-rounded-lg persona-border persona-px-3 persona-py-2 persona-text-xs\"\n    );\n    // Ensure font size matches header text (0.75rem / 12px)\n    logsPre.style.fontSize = \"0.75rem\";\n    logsPre.style.lineHeight = \"1rem\";\n    applyToolCodeBlockColors(logsPre, toolCallConfig);\n    logsPre.textContent = tool.chunks.join(\"\");\n    logsBlock.append(logsLabel, logsPre);\n    content.appendChild(logsBlock);\n  }\n\n  if (tool.status === \"complete\" && tool.result !== undefined) {\n    const resultBlock = createElement(\"div\", \"persona-space-y-1\");\n    const resultLabel = createElement(\n      \"div\",\n      \"persona-text-xs persona-text-persona-muted\"\n    );\n    if (toolCallConfig.labelTextColor) {\n      resultLabel.style.color = toolCallConfig.labelTextColor;\n    }\n    resultLabel.textContent = \"Result\";\n    const resultPre = createElement(\n      \"pre\",\n      \"persona-max-h-48 persona-overflow-auto persona-whitespace-pre-wrap persona-rounded-lg persona-border persona-px-3 persona-py-2 persona-text-xs\"\n    );\n    // Ensure font size matches header text (0.75rem / 12px)\n    resultPre.style.fontSize = \"0.75rem\";\n    resultPre.style.lineHeight = \"1rem\";\n    applyToolCodeBlockColors(resultPre, toolCallConfig);\n    resultPre.textContent = formatUnknownValue(tool.result);\n    resultBlock.append(resultLabel, resultPre);\n    content.appendChild(resultBlock);\n  }\n\n  if (tool.status === \"complete\" && typeof tool.duration === \"number\") {\n    const duration = createElement(\n      \"div\",\n      \"persona-text-xs persona-text-persona-muted\"\n    );\n    if (toolCallConfig.contentTextColor) {\n      duration.style.color = toolCallConfig.contentTextColor;\n    }\n    duration.textContent = `Duration: ${tool.duration}ms`;\n    content.appendChild(duration);\n  }\n\n  const applyToolExpansion = () => {\n    header.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n    if (toggleIcon) {\n      toggleIcon.innerHTML = \"\";\n      // Default the toggle chevron to the tool-call title color so it stays\n      // readable on whatever surface the title does. The title falls back to\n      // `.persona-text-persona-primary` (var(--persona-primary)) when no\n      // `headerTextColor` is set, so mirror that here instead of `currentColor`.\n      const iconColor =\n        toolCallConfig.toggleTextColor ||\n        toolCallConfig.headerTextColor ||\n        \"var(--persona-primary, #171717)\";\n      const chevronIcon = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 16, iconColor, 2);\n      if (chevronIcon) {\n        toggleIcon.appendChild(chevronIcon);\n      } else {\n        toggleIcon.textContent = expanded ? \"Hide\" : \"Show\";\n      }\n    }\n    content.style.display = expanded ? \"\" : \"none\";\n    collapsedPreview.style.display = expanded\n      ? \"none\"\n      : ((collapsedPreview.textContent || collapsedPreview.childNodes.length) ? \"\" : \"none\");\n  };\n\n  applyToolExpansion();\n\n  bubble.append(header, collapsedPreview, content);\n  return bubble;\n};\n\n\n\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetMessage, AgentWidgetConfig } from \"../types\";\nimport { formatUnknownValue } from \"../utils/formatting\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { WEBMCP_TOOL_PREFIX, getWebMcpToolDisplayTitle } from \"../webmcp-bridge\";\n\n/**\n * Per-message expanded/collapsed state for the technical-details section.\n * Absent means \"use the config default\" (`approval.detailsDisplay`). Lives at\n * module scope so the choice survives idiomorph re-renders, mirroring\n * `toolExpansionState` in tool-bubble.ts.\n */\nexport const approvalDetailsExpansionState = new Map<string, boolean>();\n\n/**\n * Turn a wire tool name into a user-facing label: strips the `webmcp:`\n * prefix and splits snake_case / kebab-case / camelCase into a sentence\n * (`add_to_cart` → \"Add to cart\"). Falls back to the input when nothing\n * word-like remains.\n */\nexport const humanizeToolName = (toolName: string): string => {\n  const bare = toolName.startsWith(WEBMCP_TOOL_PREFIX)\n    ? toolName.slice(WEBMCP_TOOL_PREFIX.length)\n    : toolName;\n  const words = bare\n    .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n    .split(/[_\\-\\s.]+/)\n    .filter(Boolean);\n  if (words.length === 0) return toolName;\n  const sentence = words.join(\" \").toLowerCase();\n  return sentence.charAt(0).toUpperCase() + sentence.slice(1);\n};\n\nconst resolveApprovalConfig = (config?: AgentWidgetConfig) =>\n  config?.approval !== false ? config?.approval : undefined;\n\nconst isDetailsExpanded = (\n  messageId: string,\n  config?: AgentWidgetConfig\n): boolean => {\n  const detailsMode = resolveApprovalConfig(config)?.detailsDisplay ?? \"collapsed\";\n  return approvalDetailsExpansionState.get(messageId) ?? detailsMode === \"expanded\";\n};\n\nconst applyDetailsToggleState = (\n  toggle: HTMLElement,\n  expanded: boolean,\n  config?: AgentWidgetConfig\n): void => {\n  const approvalConfig = resolveApprovalConfig(config);\n  toggle.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n  const label = toggle.querySelector(\"[data-approval-details-label]\") as HTMLElement | null;\n  if (label) {\n    label.textContent = expanded\n      ? (approvalConfig?.hideDetailsLabel ?? \"Hide details\")\n      : (approvalConfig?.showDetailsLabel ?? \"Show details\");\n  }\n  const chevronHolder = toggle.querySelector(\"[data-approval-details-chevron]\") as HTMLElement | null;\n  if (chevronHolder) {\n    chevronHolder.innerHTML = \"\";\n    const chevron = renderLucideIcon(expanded ? \"chevron-up\" : \"chevron-down\", 14, \"currentColor\", 2);\n    if (chevron) {\n      chevronHolder.appendChild(chevron);\n    }\n  }\n};\n\n/**\n * Sync the technical-details section (toggle label/chevron + visibility) with\n * `approvalDetailsExpansionState`. Called from the ui.ts expansion event\n * delegation after a toggle click.\n */\nexport const updateApprovalDetailsUI = (\n  messageId: string,\n  bubble: HTMLElement,\n  config?: AgentWidgetConfig\n): void => {\n  const toggle = bubble.querySelector('button[data-bubble-type=\"approval\"]') as HTMLElement | null;\n  const details = bubble.querySelector(\"[data-approval-details]\") as HTMLElement | null;\n  if (!toggle || !details) return;\n  const expanded = isDetailsExpanded(messageId, config);\n  applyDetailsToggleState(toggle, expanded, config);\n  details.style.display = expanded ? \"\" : \"none\";\n};\n\n/**\n * Update an existing approval bubble's UI after status changes.\n */\nexport const updateApprovalBubbleUI = (\n  _messageId: string,\n  bubble: HTMLElement,\n  config?: AgentWidgetConfig,\n  approval?: AgentWidgetMessage[\"approval\"]\n): void => {\n  if (!approval) return;\n\n  const approvalConfig = config?.approval !== false ? config?.approval : undefined;\n\n  // Update status badge\n  const statusBadge = bubble.querySelector('[data-approval-status]') as HTMLElement;\n  if (statusBadge) {\n    statusBadge.textContent = approval.status === \"approved\" ? \"Approved\"\n      : approval.status === \"denied\" ? \"Denied\"\n      : approval.status === \"timeout\" ? \"Timeout\"\n      : \"Pending\";\n\n    // Update badge color\n    if (approval.status === \"approved\") {\n      statusBadge.className = \"persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium\";\n      statusBadge.style.backgroundColor = \"var(--persona-palette-colors-success-100, #dcfce7)\";\n      statusBadge.style.color = \"var(--persona-palette-colors-success-700, #15803d)\";\n    } else if (approval.status === \"denied\") {\n      statusBadge.className = \"persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium\";\n      statusBadge.style.backgroundColor = \"var(--persona-palette-colors-error-100, #fee2e2)\";\n      statusBadge.style.color = \"var(--persona-palette-colors-error-700, #b91c1c)\";\n    } else if (approval.status === \"timeout\") {\n      statusBadge.className = \"persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium\";\n      statusBadge.style.backgroundColor = \"var(--persona-palette-colors-warning-100, #fef3c7)\";\n      statusBadge.style.color = \"var(--persona-palette-colors-warning-700, #b45309)\";\n    }\n    statusBadge.setAttribute(\"data-approval-status\", approval.status);\n  }\n\n  // Update icon\n  const iconContainer = bubble.querySelector('[data-approval-icon]') as HTMLElement;\n  if (iconContainer) {\n    iconContainer.innerHTML = \"\";\n    const iconName = approval.status === \"denied\" ? \"shield-x\"\n      : approval.status === \"timeout\" ? \"shield-alert\"\n      : \"shield-check\";\n    const iconColor = approval.status === \"approved\" ? \"var(--persona-feedback-success, #16a34a)\"\n      : approval.status === \"denied\" ? \"var(--persona-feedback-error, #dc2626)\"\n      : approval.status === \"timeout\" ? \"var(--persona-feedback-warning, #ca8a04)\"\n      : (approvalConfig?.titleColor ?? \"currentColor\");\n    const icon = renderLucideIcon(iconName, 20, iconColor, 2);\n    if (icon) {\n      iconContainer.appendChild(icon);\n    }\n  }\n\n  // Show/hide buttons based on status\n  const buttonsContainer = bubble.querySelector('[data-approval-buttons]') as HTMLElement;\n  if (buttonsContainer) {\n    buttonsContainer.style.display = approval.status === \"pending\" ? \"\" : \"none\";\n  }\n};\n\n/**\n * Create an approval bubble element for inline display in the chat.\n */\nexport const createApprovalBubble = (\n  message: AgentWidgetMessage,\n  config?: AgentWidgetConfig\n): HTMLElement => {\n  const approval = message.approval;\n  const approvalConfig = config?.approval !== false ? config?.approval : undefined;\n  const isPending = approval?.status === \"pending\";\n\n  const bubble = createElement(\n    \"div\",\n    [\n      \"persona-approval-bubble\",\n      \"persona-w-full\",\n      \"persona-max-w-[85%]\",\n      \"persona-rounded-2xl\",\n      \"persona-border\",\n      \"persona-shadow-sm\",\n      \"persona-overflow-hidden\",\n    ].join(\" \")\n  );\n\n  // Set id for idiomorph matching\n  bubble.id = `bubble-${message.id}`;\n  bubble.setAttribute(\"data-message-id\", message.id);\n\n  // Apply styling: use semantic tokens with config overrides\n  bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? \"var(--persona-approval-bg, #fefce8)\";\n  bubble.style.borderColor = approvalConfig?.borderColor ?? \"var(--persona-approval-border, #fef08a)\";\n  bubble.style.boxShadow =\n    approvalConfig?.shadow !== undefined\n      ? (approvalConfig.shadow.trim() === \"\" ? \"none\" : approvalConfig.shadow)\n      : \"var(--persona-approval-shadow, 0 5px 15px rgba(15, 23, 42, 0.08))\";\n\n  if (!approval) {\n    return bubble;\n  }\n\n  // Header section with icon, title, and status badge\n  const header = createElement(\n    \"div\",\n    \"persona-flex persona-items-start persona-gap-3 persona-px-4 persona-py-3\"\n  );\n\n  // Icon container\n  const iconContainer = createElement(\"div\", \"persona-flex-shrink-0 persona-mt-0.5\");\n  iconContainer.setAttribute(\"data-approval-icon\", \"true\");\n  const iconName = approval.status === \"denied\" ? \"shield-x\"\n    : approval.status === \"timeout\" ? \"shield-alert\"\n    : \"shield-check\";\n  const iconColor = approval.status === \"approved\" ? \"var(--persona-feedback-success, #16a34a)\"\n    : approval.status === \"denied\" ? \"var(--persona-feedback-error, #dc2626)\"\n    : approval.status === \"timeout\" ? \"var(--persona-feedback-warning, #ca8a04)\"\n    : (approvalConfig?.titleColor ?? \"currentColor\");\n  const icon = renderLucideIcon(iconName, 20, iconColor, 2);\n  if (icon) {\n    iconContainer.appendChild(icon);\n  }\n\n  // Content area\n  const content = createElement(\"div\", \"persona-flex-1 persona-min-w-0\");\n\n  // Title row with status badge\n  const titleRow = createElement(\"div\", \"persona-flex persona-items-center persona-gap-2\");\n  const title = createElement(\"span\", \"persona-text-sm persona-font-medium persona-text-persona-primary\");\n  if (approvalConfig?.titleColor) {\n    title.style.color = approvalConfig.titleColor;\n  }\n  title.textContent = approvalConfig?.title ?? \"Approval Required\";\n  titleRow.appendChild(title);\n\n  // Status badge (shown when resolved)\n  if (!isPending) {\n    const badge = createElement(\"span\", \"persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium\");\n    badge.setAttribute(\"data-approval-status\", approval.status);\n    if (approval.status === \"approved\") {\n      badge.style.backgroundColor = \"var(--persona-palette-colors-success-100, #dcfce7)\";\n      badge.style.color = \"var(--persona-palette-colors-success-700, #15803d)\";\n      badge.textContent = \"Approved\";\n    } else if (approval.status === \"denied\") {\n      badge.style.backgroundColor = \"var(--persona-palette-colors-error-100, #fee2e2)\";\n      badge.style.color = \"var(--persona-palette-colors-error-700, #b91c1c)\";\n      badge.textContent = \"Denied\";\n    } else if (approval.status === \"timeout\") {\n      badge.style.backgroundColor = \"var(--persona-palette-colors-warning-100, #fef3c7)\";\n      badge.style.color = \"var(--persona-palette-colors-warning-700, #b45309)\";\n      badge.textContent = \"Timeout\";\n    }\n    titleRow.appendChild(badge);\n  }\n\n  content.appendChild(titleRow);\n\n  // User-facing summary line. The wire `description` is the tool's\n  // agent-facing description (prompt prose, usage rules), so it is not shown\n  // here: it lives in the collapsible details section below. Label priority:\n  // formatDescription → declared WebMCP `title` → humanized tool name →\n  // raw description (no tool name at all).\n  const isWebMcpTool =\n    approval.toolType === \"webmcp\" ||\n    approval.toolName.startsWith(WEBMCP_TOOL_PREFIX);\n  const declaredTitle = isWebMcpTool\n    ? getWebMcpToolDisplayTitle(approval.toolName)\n    : undefined;\n  const summaryFromConfig = approvalConfig?.formatDescription?.({\n    toolName: approval.toolName,\n    toolType: approval.toolType,\n    description: approval.description,\n    parameters: approval.parameters,\n    ...(declaredTitle ? { displayTitle: declaredTitle } : {}),\n    ...(approval.reason ? { reason: approval.reason } : {}),\n  });\n  const summaryFallsBackToDescription = !approval.toolName;\n  const summaryText =\n    summaryFromConfig ||\n    (summaryFallsBackToDescription\n      ? approval.description\n      : `The assistant wants to use “${declaredTitle ?? humanizeToolName(approval.toolName)}”.`);\n\n  const summary = createElement(\"p\", \"persona-text-sm persona-mt-0.5 persona-text-persona-muted\");\n  summary.setAttribute(\"data-approval-summary\", \"true\");\n  if (approvalConfig?.descriptionColor) {\n    summary.style.color = approvalConfig.descriptionColor;\n  }\n  summary.textContent = summaryText;\n  content.appendChild(summary);\n\n  // Agent-authored justification for this specific call. It is the agent's\n  // own claim about its intent (attacker-writable under prompt injection), so\n  // it is rendered as plain text via textContent, never markdown/HTML, and\n  // explicitly attributed to the agent rather than spoken in system voice.\n  if (approval.reason) {\n    const reasonLine = createElement(\"p\", \"persona-text-sm persona-mt-1 persona-text-persona-muted\");\n    reasonLine.setAttribute(\"data-approval-reason\", \"true\");\n    if (approvalConfig?.reasonColor) {\n      reasonLine.style.color = approvalConfig.reasonColor;\n    } else if (approvalConfig?.descriptionColor) {\n      reasonLine.style.color = approvalConfig.descriptionColor;\n    }\n    const reasonLabel = createElement(\"span\", \"persona-font-medium\");\n    reasonLabel.textContent = `${approvalConfig?.reasonLabel ?? \"Agent's stated reason:\"} `;\n    reasonLine.appendChild(reasonLabel);\n    reasonLine.appendChild(document.createTextNode(approval.reason));\n    content.appendChild(reasonLine);\n  }\n\n  // Technical details: agent-facing description + raw parameters JSON,\n  // collapsed behind a toggle by default (`approval.detailsDisplay`).\n  const detailsMode = approvalConfig?.detailsDisplay ?? \"collapsed\";\n  const showDescriptionInDetails = Boolean(approval.description) && !summaryFallsBackToDescription;\n  const hasDetails = showDescriptionInDetails || Boolean(approval.parameters);\n  if (detailsMode !== \"hidden\" && hasDetails) {\n    const expanded = isDetailsExpanded(message.id, config);\n\n    const toggle = createElement(\n      \"button\",\n      \"persona-inline-flex persona-items-center persona-gap-1 persona-mt-1 persona-p-0 persona-border-none persona-bg-transparent persona-text-xs persona-font-medium persona-cursor-pointer persona-text-persona-muted\"\n    ) as HTMLButtonElement;\n    toggle.type = \"button\";\n    toggle.setAttribute(\"data-expand-header\", \"true\");\n    toggle.setAttribute(\"data-bubble-type\", \"approval\");\n    if (approvalConfig?.descriptionColor) {\n      toggle.style.color = approvalConfig.descriptionColor;\n    }\n    const toggleLabel = createElement(\"span\");\n    toggleLabel.setAttribute(\"data-approval-details-label\", \"true\");\n    const chevronHolder = createElement(\"span\", \"persona-inline-flex persona-items-center\");\n    chevronHolder.setAttribute(\"data-approval-details-chevron\", \"true\");\n    toggle.append(toggleLabel, chevronHolder);\n    applyDetailsToggleState(toggle, expanded, config);\n    content.appendChild(toggle);\n\n    const details = createElement(\"div\");\n    details.setAttribute(\"data-approval-details\", \"true\");\n    details.style.display = expanded ? \"\" : \"none\";\n\n    if (showDescriptionInDetails) {\n      const description = createElement(\"p\", \"persona-text-sm persona-mt-1 persona-text-persona-muted\");\n      if (approvalConfig?.descriptionColor) {\n        description.style.color = approvalConfig.descriptionColor;\n      }\n      description.textContent = approval.description;\n      details.appendChild(description);\n    }\n\n    if (approval.parameters) {\n      const paramsPre = createElement(\n        \"pre\",\n        \"persona-mt-2 persona-text-xs persona-p-2 persona-rounded persona-overflow-x-auto persona-max-h-32 persona-bg-persona-container persona-text-persona-primary\"\n      );\n      if (approvalConfig?.parameterBackgroundColor) {\n        paramsPre.style.backgroundColor = approvalConfig.parameterBackgroundColor;\n      }\n      if (approvalConfig?.parameterTextColor) {\n        paramsPre.style.color = approvalConfig.parameterTextColor;\n      }\n      paramsPre.style.fontSize = \"0.75rem\";\n      paramsPre.style.lineHeight = \"1rem\";\n      paramsPre.textContent = formatUnknownValue(approval.parameters);\n      details.appendChild(paramsPre);\n    }\n\n    content.appendChild(details);\n  }\n\n  // Action buttons (only shown when pending)\n  if (isPending) {\n    const buttonsContainer = createElement(\"div\", \"persona-flex persona-gap-2 persona-mt-2\");\n    buttonsContainer.setAttribute(\"data-approval-buttons\", \"true\");\n\n    // Approve button\n    const approveBtn = createElement(\"button\", \"persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-border-none persona-cursor-pointer\") as HTMLButtonElement;\n    approveBtn.type = \"button\";\n    approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? \"var(--persona-approval-approve-bg, #22c55e)\";\n    approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? \"#ffffff\";\n    approveBtn.setAttribute(\"data-approval-action\", \"approve\");\n    const approveIcon = renderLucideIcon(\"shield-check\", 14, approvalConfig?.approveButtonTextColor ?? \"#ffffff\", 2);\n    if (approveIcon) {\n      approveIcon.style.marginRight = \"4px\";\n      approveBtn.appendChild(approveIcon);\n    }\n    const approveLabel = document.createTextNode(approvalConfig?.approveLabel ?? \"Approve\");\n    approveBtn.appendChild(approveLabel);\n\n    // Deny button\n    const denyBtn = createElement(\"button\", \"persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-cursor-pointer\") as HTMLButtonElement;\n    denyBtn.type = \"button\";\n    denyBtn.style.backgroundColor = approvalConfig?.denyButtonColor ?? \"transparent\";\n    denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? \"var(--persona-feedback-error, #dc2626)\";\n    denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : \"var(--persona-palette-colors-error-200, #fca5a5)\"}`;\n    denyBtn.setAttribute(\"data-approval-action\", \"deny\");\n    const denyIcon = renderLucideIcon(\"shield-x\", 14, approvalConfig?.denyButtonTextColor ?? \"var(--persona-feedback-error, #dc2626)\", 2);\n    if (denyIcon) {\n      denyIcon.style.marginRight = \"4px\";\n      denyBtn.appendChild(denyIcon);\n    }\n    const denyLabel = document.createTextNode(approvalConfig?.denyLabel ?? \"Deny\");\n    denyBtn.appendChild(denyLabel);\n\n    buttonsContainer.append(approveBtn, denyBtn);\n    content.appendChild(buttonsContainer);\n  }\n\n  header.append(iconContainer, content);\n  bubble.appendChild(header);\n\n  return bubble;\n};\n","// Plugin Kit: small, dependency-free utilities for authoring Persona plugins.\n//\n// Plugin render hooks (`renderApproval`, `renderAskUserQuestion`, `renderMessage`,\n// …) return a detached `HTMLElement` that the widget morphs into the transcript.\n// Two needs come up again and again when that element is more than static markup:\n//\n//   1. Injecting the plugin's own CSS so it survives the widget's Shadow-DOM mode\n//      (a plain `document.head` <style> does NOT pierce a shadow root).\n//   2. Floating UI, dropdowns, menus, tooltips, that must overlay the rest of\n//      the widget and not be clipped by the transcript's scroll container.\n//\n// Both are easy to get subtly wrong, so they live here as a supported, optional\n// subpath: `@runtypelabs/persona/plugin-kit`. Importing it costs nothing unless\n// you use it, and nothing here touches the widget's core bundle.\n//\n// ```ts\n// import { injectStyles, createPopover, isEditableEventTarget } from\n//   \"@runtypelabs/persona/plugin-kit\";\n// ```\n\n/* ============================================================\n   Shadow-safe style injection\n   ============================================================ */\n\n/**\n * Resolve the root a node's styles should live in.\n *\n * Returns the node's `ShadowRoot` when it (or an ancestor) is shadowed, the\n * owning `Document` otherwise. A `<style>` placed in this root reaches the node\n * regardless of whether the widget runs in light or shadow DOM. For a detached\n * node (one not yet mounted) this falls back to the owning document.\n */\nexport function getStyleRoot(node: Node): Document | ShadowRoot {\n  const root = node.getRootNode?.();\n  if (root instanceof ShadowRoot) return root;\n  if (root instanceof Document) return root;\n  return node.ownerDocument ?? document;\n}\n\nfunction injectInto(root: Document | ShadowRoot, id: string, css: string): void {\n  const container: Document | ShadowRoot | HTMLHeadElement =\n    root instanceof Document ? root.head : root;\n  const escaped = id.replace(/[\"\\\\]/g, \"\\\\$&\");\n  if (container.querySelector(`style[data-persona-plugin-style=\"${escaped}\"]`)) {\n    return; // Already present in this root: idempotent.\n  }\n  const doc = root instanceof Document ? root : root.ownerDocument ?? document;\n  const style = doc.createElement(\"style\");\n  style.setAttribute(\"data-persona-plugin-style\", id);\n  style.textContent = css;\n  container.appendChild(style);\n}\n\n/**\n * Inject a plugin's CSS once into the correct root: the widget's shadow root\n * when shadowed, the document head otherwise. Idempotent: keyed by `id`, so it\n * is safe to call on every render.\n *\n * Pass the element you're about to return from a render hook. While building, an\n * element is detached and its eventual root is unknown, so this injects into the\n * owning document immediately (covering the default light-DOM case with no\n * flash) and then, on the next microtask: after the widget has mounted the\n * element: re-resolves and also injects into its shadow root if it landed in\n * one. You may also pass an explicit `Document` or `ShadowRoot`.\n *\n * @example\n * ```ts\n * renderApproval: ({ message, approve, deny }) => {\n *   const card = buildCard(message.approval, approve, deny);\n *   injectStyles(card, \"my-approval-plugin\", CSS);\n *   return card;\n * }\n * ```\n */\nexport function injectStyles(\n  target: Node | Document | ShadowRoot,\n  id: string,\n  css: string\n): void {\n  if (target instanceof Document || target instanceof ShadowRoot) {\n    injectInto(target, id, css);\n    return;\n  }\n  const node = target;\n  if (node.isConnected) {\n    injectInto(getStyleRoot(node), id, css);\n    return;\n  }\n  // Detached (built but not yet mounted): inject into the owning document now,\n  // then re-resolve after mount so shadow-DOM widgets also get the <style>.\n  const doc = node.ownerDocument ?? document;\n  injectInto(doc, id, css);\n  queueMicrotask(() => {\n    const root = getStyleRoot(node);\n    if (root !== doc) injectInto(root, id, css);\n  });\n}\n\n/* ============================================================\n   Floating popover\n   ============================================================ */\n\nexport type PopoverPlacement =\n  | \"bottom-start\"\n  | \"bottom-end\"\n  | \"top-start\"\n  | \"top-end\";\n\nexport interface PopoverOptions {\n  /** Element the popover is positioned against. */\n  anchor: HTMLElement;\n  /** The floating element (menu, tooltip, panel). Built detached; mounted on `open`. */\n  content: HTMLElement;\n  /** Where to place `content` relative to `anchor`. Default `\"bottom-start\"`. */\n  placement?: PopoverPlacement;\n  /** Gap in px between anchor and content. Default `6`. */\n  offset?: number;\n  /** Set `content`'s `min-width` to the anchor's width. Default `false`. */\n  matchAnchorWidth?: boolean;\n  /**\n   * Inline `z-index` for `content`. Default `2147483000` so it overlays the rest\n   * of the widget. Pass `null` to leave z-index to your own CSS.\n   */\n  zIndex?: number | null;\n  /**\n   * Where to mount `content`. Defaults to the anchor's shadow root (when\n   * shadowed) or `document.body`: keeping it inside the same style + stacking\n   * scope as the anchor while escaping the transcript's scroll clipping.\n   */\n  container?: HTMLElement | ShadowRoot;\n  onOpen?: () => void;\n  /** Fired when the popover closes by itself (outside click or the anchor leaving the DOM). */\n  onDismiss?: (reason: \"outside\" | \"anchor-removed\") => void;\n}\n\nexport interface PopoverHandle {\n  readonly isOpen: boolean;\n  open(): void;\n  close(): void;\n  toggle(): void;\n  /** Recompute position from the current anchor rect (called automatically on scroll/resize). */\n  reposition(): void;\n  /** Close and release all listeners; the handle is inert afterward. */\n  destroy(): void;\n}\n\nfunction defaultContainer(anchor: HTMLElement): HTMLElement | ShadowRoot {\n  const root = anchor.getRootNode?.();\n  if (root instanceof ShadowRoot) return root;\n  return (anchor.ownerDocument ?? document).body;\n}\n\n/**\n * A floating popover anchored to an element: `fixed`-positioned (so it overlays\n * the rest of the widget and isn't clipped by scroll containers), dismissed on\n * outside pointerdown, repositioned on scroll/resize, and auto-closed if the\n * anchor leaves the DOM. Mount your styles with {@link injectStyles}.\n *\n * @example\n * ```ts\n * const popover = createPopover({\n *   anchor: splitButton,\n *   content: menu,\n *   placement: \"bottom-start\",\n *   matchAnchorWidth: true,\n * });\n * caret.addEventListener(\"click\", () => popover.toggle());\n * // on teardown: popover.destroy();\n * ```\n */\nexport function createPopover(options: PopoverOptions): PopoverHandle {\n  const {\n    anchor,\n    content,\n    placement = \"bottom-start\",\n    offset = 6,\n    matchAnchorWidth = false,\n    zIndex = 2147483000,\n    onOpen,\n    onDismiss,\n  } = options;\n\n  const container = options.container ?? defaultContainer(anchor);\n  let open = false;\n  let detach: (() => void) | null = null;\n\n  const reposition = (): void => {\n    if (!open) return;\n    const rect = anchor.getBoundingClientRect();\n    content.style.position = \"fixed\";\n    if (matchAnchorWidth) content.style.minWidth = `${rect.width}px`;\n\n    const top =\n      placement === \"top-start\" || placement === \"top-end\"\n        ? rect.top - offset - content.getBoundingClientRect().height\n        : rect.bottom + offset;\n\n    const left =\n      placement === \"bottom-end\" || placement === \"top-end\"\n        ? rect.right - content.getBoundingClientRect().width\n        : rect.left;\n\n    content.style.top = `${top}px`;\n    content.style.left = `${left}px`;\n  };\n\n  const close = (): void => {\n    if (!open) return;\n    open = false;\n    if (detach) {\n      detach();\n      detach = null;\n    }\n    content.remove();\n  };\n\n  const doOpen = (): void => {\n    if (open) return;\n    open = true;\n    if (zIndex != null) content.style.zIndex = String(zIndex);\n    container.appendChild(content);\n    reposition();\n\n    const ownerWindow = (anchor.ownerDocument ?? document).defaultView ?? window;\n    const ownerDocument = anchor.ownerDocument ?? document;\n\n    const onReposition = (): void => {\n      if (!anchor.isConnected) {\n        close();\n        onDismiss?.(\"anchor-removed\");\n        return;\n      }\n      reposition();\n    };\n    const onOutside = (event: Event): void => {\n      const path =\n        typeof event.composedPath === \"function\" ? event.composedPath() : [];\n      if (path.includes(content) || path.includes(anchor)) return;\n      close();\n      onDismiss?.(\"outside\");\n    };\n\n    // Defer arming so the click that opened the popover doesn't dismiss it.\n    const armId = ownerWindow.setTimeout(() => {\n      ownerDocument.addEventListener(\"pointerdown\", onOutside, true);\n    }, 0);\n    ownerWindow.addEventListener(\"scroll\", onReposition, true);\n    ownerWindow.addEventListener(\"resize\", onReposition);\n\n    detach = () => {\n      ownerWindow.clearTimeout(armId);\n      ownerDocument.removeEventListener(\"pointerdown\", onOutside, true);\n      ownerWindow.removeEventListener(\"scroll\", onReposition, true);\n      ownerWindow.removeEventListener(\"resize\", onReposition);\n    };\n\n    onOpen?.();\n  };\n\n  return {\n    get isOpen() {\n      return open;\n    },\n    open: doOpen,\n    close,\n    toggle: () => (open ? close() : doOpen()),\n    reposition,\n    destroy: close,\n  };\n}\n\n/* ============================================================\n   Keyboard helpers\n   ============================================================ */\n\n/**\n * Whether an event originated from an editable element (`<input>`, `<textarea>`,\n * or `contenteditable`). Use it to avoid hijacking keys like Enter/Escape while\n * the user is typing in the composer.\n *\n * Inspects the composed path, so it works for events that cross the widget's\n * Shadow-DOM boundary (where `event.target` is retargeted to the host).\n */\nexport function isEditableEventTarget(event: Event): boolean {\n  const path =\n    typeof event.composedPath === \"function\" ? event.composedPath() : [];\n  return path.some(\n    (el) =>\n      el instanceof HTMLElement &&\n      (el.tagName === \"INPUT\" ||\n        el.tagName === \"TEXTAREA\" ||\n        el.isContentEditable)\n  );\n}\n","/**\n * Built-in default approval renderer.\n *\n * Renders the neutral \"permission card\": a tool icon, a \"The assistant wants to\n * use <tool>\" title, the call arguments collapsed behind a \"show more\" header\n * chevron, and an action row. By default the row is a single \"Allow\" (allow\n * once) + \"Deny\". When `config.approval.enableAlwaysAllow` is true it becomes a\n * split \"Always allow ⏎\" primary with an \"Allow once ⌘⏎\" dropdown plus\n * \"Deny Esc\", with keyboard shortcuts — that affordance is opt-in because\n * \"Always allow\" only means anything if the integrator persists the policy via\n * `onDecision`'s `remember` flag (needs a backend).\n *\n * Installed as an internal `renderApproval` plugin (see ui.ts) so it rides the\n * existing plugin stub-and-hydrate + teardown path and survives idiomorph\n * re-renders. A user-supplied `renderApproval` plugin still fully overrides it.\n *\n * Resolved states mirror the example plugin: approved → render nothing (the\n * tool call takes over the transcript); denied/timeout → a subtle one-line\n * trace.\n */\nimport { createElement } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { formatUnknownValue } from \"../utils/formatting\";\nimport { WEBMCP_TOOL_PREFIX, getWebMcpToolDisplayTitle } from \"../webmcp-bridge\";\nimport { createPopover, isEditableEventTarget, type PopoverHandle } from \"../plugin-kit\";\nimport type {\n  AgentWidgetMessage,\n  AgentWidgetConfig,\n  AgentWidgetApprovalConfig,\n} from \"../types\";\nimport type { AgentWidgetPlugin } from \"../plugins/types\";\nimport { humanizeToolName, approvalDetailsExpansionState } from \"./approval-bubble\";\n\ntype Approval = NonNullable<AgentWidgetMessage[\"approval\"]>;\ntype Decide = (options?: { remember?: boolean }) => void;\n\n/**\n * Per-widget-instance runtime state. The document `keydown` handler (re-bound on\n * each render to the freshest approve/deny closures) and the \"Allow once\"\n * popover are torn down when the approval resolves, the bubble rebuilds, or the\n * widget is destroyed. This lives on the plugin instance (NOT module scope) so\n * tearing down one widget never clears listeners/popovers another widget on the\n * same page still owns.\n */\ninterface InstanceState {\n  keyHandlers: Map<string, (e: KeyboardEvent) => void>;\n  popovers: Map<string, PopoverHandle>;\n  // Order in which approvals FIRST became pending — not re-render order. The\n  // newest pending approval (bottom of the thread) owns the keyboard shortcuts,\n  // so Enter/Esc don't fire on every pending card at once, and an older card\n  // re-rendering can't steal ownership from a newer one.\n  pendingOrder: string[];\n  latestPendingApprovalId: string | null;\n}\n\nconst createInstanceState = (): InstanceState => ({\n  keyHandlers: new Map(),\n  popovers: new Map(),\n  pendingOrder: [],\n  latestPendingApprovalId: null,\n});\n\n// Drop a message's transient listener + popover WITHOUT touching its place in\n// the pending order. Used when a still-pending card re-renders and rebinds fresh\n// closures (so a rebuild of an older card doesn't reorder it to \"newest\").\nconst detachMessage = (state: InstanceState, messageId: string): void => {\n  const prevKey = state.keyHandlers.get(messageId);\n  if (prevKey) {\n    document.removeEventListener(\"keydown\", prevKey);\n    state.keyHandlers.delete(messageId);\n  }\n  const popover = state.popovers.get(messageId);\n  if (popover) {\n    popover.destroy();\n    state.popovers.delete(messageId);\n  }\n};\n\n// Fully retire a message (resolved, denied, or widget destroyed): detach its\n// listener/popover AND remove it from the pending order. If the keyboard owner\n// just went away, promote the newest remaining pending approval so Enter/Esc\n// keep working instead of going dead while another pending card remains.\nconst teardownMessage = (state: InstanceState, messageId: string): void => {\n  detachMessage(state, messageId);\n  const idx = state.pendingOrder.indexOf(messageId);\n  if (idx !== -1) state.pendingOrder.splice(idx, 1);\n  if (state.latestPendingApprovalId === messageId) {\n    state.latestPendingApprovalId = state.pendingOrder.length\n      ? state.pendingOrder[state.pendingOrder.length - 1]\n      : null;\n  }\n};\n\nconst resolveApprovalConfig = (\n  config?: AgentWidgetConfig\n): AgentWidgetApprovalConfig | undefined =>\n  config?.approval !== false ? config?.approval : undefined;\n\nconst isDetailsExpanded = (\n  messageId: string,\n  approvalConfig?: AgentWidgetApprovalConfig\n): boolean => {\n  const mode = approvalConfig?.detailsDisplay ?? \"collapsed\";\n  return approvalDetailsExpansionState.get(messageId) ?? mode === \"expanded\";\n};\n\nconst kbd = (label: string): HTMLElement => {\n  const el = createElement(\"span\", \"persona-approval-kbd\");\n  el.textContent = label;\n  return el;\n};\n\n// Title: \"The assistant wants to use <tool>\" with an optional \"from <source>\"\n// when a non-WebMCP `toolType` source label is present. `formatDescription`\n// fully overrides the line. Mirrors the built-in bubble's label priority\n// (formatDescription → declared WebMCP title → humanized tool name).\nconst buildTitle = (\n  approval: Approval,\n  approvalConfig?: AgentWidgetApprovalConfig\n): HTMLElement => {\n  const title = createElement(\"span\", \"persona-approval-title\");\n  if (approvalConfig?.titleColor) title.style.color = approvalConfig.titleColor;\n\n  const isWebMcp =\n    approval.toolType === \"webmcp\" || approval.toolName.startsWith(WEBMCP_TOOL_PREFIX);\n  const declaredTitle = isWebMcp\n    ? getWebMcpToolDisplayTitle(approval.toolName)\n    : undefined;\n\n  const custom = approvalConfig?.formatDescription?.({\n    toolName: approval.toolName,\n    toolType: approval.toolType,\n    description: approval.description ?? \"\",\n    parameters: approval.parameters,\n    ...(declaredTitle ? { displayTitle: declaredTitle } : {}),\n    ...(approval.reason ? { reason: approval.reason } : {}),\n  });\n  if (custom) {\n    title.textContent = custom;\n    return title;\n  }\n\n  const toolDisplay = declaredTitle ?? humanizeToolName(approval.toolName);\n  const source =\n    approval.toolType && approval.toolType !== \"webmcp\" ? approval.toolType : null;\n  title.append(\"The assistant wants to use \");\n  const toolStrong = document.createElement(\"strong\");\n  toolStrong.textContent = toolDisplay;\n  title.appendChild(toolStrong);\n  if (source) {\n    title.append(\" from \");\n    const srcStrong = document.createElement(\"strong\");\n    srcStrong.textContent = source;\n    title.appendChild(srcStrong);\n  }\n  return title;\n};\n\nconst buildResolvedTrace = (approval: Approval): HTMLElement => {\n  const row = createElement(\"div\", \"persona-approval-resolved\");\n  const icon = renderLucideIcon(\"ban\", 15, \"currentColor\", 2);\n  if (icon) row.appendChild(icon);\n  const name = createElement(\"span\", \"persona-approval-resolved-name\");\n  name.textContent = approval.toolName ? humanizeToolName(approval.toolName) : \"Tool\";\n  row.append(name, document.createTextNode(approval.status === \"timeout\" ? \" timed out\" : \" denied\"));\n  return row;\n};\n\nconst buildPending = (\n  state: InstanceState,\n  message: AgentWidgetMessage,\n  approval: Approval,\n  approvalConfig: AgentWidgetApprovalConfig | undefined,\n  approve: Decide,\n  deny: Decide,\n  enableAlways: boolean\n): HTMLElement => {\n  const card = createElement(\"div\", \"persona-approval-card persona-shadow-sm\");\n  card.id = `bubble-${message.id}`;\n  card.setAttribute(\"data-message-id\", message.id);\n  card.setAttribute(\"data-bubble-type\", \"approval\");\n  if (approvalConfig?.backgroundColor) card.style.background = approvalConfig.backgroundColor;\n  if (approvalConfig?.borderColor) card.style.borderColor = approvalConfig.borderColor;\n  if (approvalConfig?.shadow !== undefined) {\n    card.style.boxShadow = approvalConfig.shadow.trim() === \"\" ? \"none\" : approvalConfig.shadow;\n  }\n\n  const detailsMode = approvalConfig?.detailsDisplay ?? \"collapsed\";\n  // The disclosure surfaces the agent-facing description (prompt prose, usage\n  // rules) and the raw call parameters. `buildTitle` never falls back to the\n  // raw description (it uses formatDescription → declared title → humanized\n  // name), so the description is only ever visible here. Mirrors the legacy\n  // bubble, which also opened a disclosure when only a description was present.\n  const hasDescription = Boolean(approval.description) && detailsMode !== \"hidden\";\n  const hasParams = approval.parameters != null && detailsMode !== \"hidden\";\n  const hasDetails = hasDescription || hasParams;\n  const expanded = hasDetails && isDetailsExpanded(message.id, approvalConfig);\n\n  // The card uses the whole header as the disclosure toggle (chevron-only, no\n  // separate text button), but the legacy `showDetailsLabel`/`hideDetailsLabel`\n  // strings are still honored as the toggle's accessible label so integrators\n  // who localized them keep a meaningful, customizable name on the control.\n  const showDetailsLabel = approvalConfig?.showDetailsLabel ?? \"Show details\";\n  const hideDetailsLabel = approvalConfig?.hideDetailsLabel ?? \"Hide details\";\n\n  // Header. When a disclosure exists, the whole header toggles its visibility.\n  const head = createElement(\"button\", \"persona-approval-head\") as HTMLButtonElement;\n  head.type = \"button\";\n  if (hasDetails) {\n    head.setAttribute(\"data-action\", \"toggle-params\");\n    head.setAttribute(\"aria-expanded\", expanded ? \"true\" : \"false\");\n    head.setAttribute(\"aria-label\", expanded ? hideDetailsLabel : showDetailsLabel);\n  } else {\n    head.setAttribute(\"data-static\", \"true\");\n  }\n\n  const logo = createElement(\"span\", \"persona-approval-logo\");\n  const glyph = renderLucideIcon(\"shield-check\", 16, \"currentColor\", 2);\n  if (glyph) logo.appendChild(glyph);\n  head.appendChild(logo);\n\n  const title = buildTitle(approval, approvalConfig);\n  if (hasDetails) {\n    const toggle = createElement(\"span\", \"persona-approval-toggle\");\n    toggle.setAttribute(\"aria-hidden\", \"true\");\n    const chevron = renderLucideIcon(\"chevron-down\", 14, \"currentColor\", 2);\n    if (chevron) toggle.appendChild(chevron);\n    title.append(\" \");\n    title.appendChild(toggle);\n  }\n  head.appendChild(title);\n  card.appendChild(head);\n\n  const body = createElement(\"div\", \"persona-approval-body\");\n\n  if (hasDetails) {\n    const details = createElement(\"div\", \"persona-approval-details\");\n    details.setAttribute(\"data-role\", \"params\");\n    details.hidden = !expanded;\n\n    if (hasDescription) {\n      const desc = createElement(\"p\", \"persona-approval-desc\");\n      if (approvalConfig?.descriptionColor) desc.style.color = approvalConfig.descriptionColor;\n      desc.textContent = approval.description as string;\n      details.appendChild(desc);\n    }\n\n    if (hasParams) {\n      const pre = createElement(\"pre\", \"persona-approval-params\");\n      if (approvalConfig?.parameterBackgroundColor) pre.style.background = approvalConfig.parameterBackgroundColor;\n      if (approvalConfig?.parameterTextColor) pre.style.color = approvalConfig.parameterTextColor;\n      pre.textContent = formatUnknownValue(approval.parameters);\n      details.appendChild(pre);\n    }\n\n    body.appendChild(details);\n  }\n\n  // Agent-authored justification: attacker-writable, so plain text + attributed.\n  if (approval.reason) {\n    const reasonLine = createElement(\"p\", \"persona-approval-reason\");\n    if (approvalConfig?.reasonColor) reasonLine.style.color = approvalConfig.reasonColor;\n    else if (approvalConfig?.descriptionColor) reasonLine.style.color = approvalConfig.descriptionColor;\n    const label = createElement(\"span\", \"persona-approval-reason-label\");\n    label.textContent = `${approvalConfig?.reasonLabel ?? \"Agent's stated reason:\"} `;\n    reasonLine.append(label, document.createTextNode(approval.reason));\n    body.appendChild(reasonLine);\n  }\n\n  const actions = createElement(\"div\", \"persona-approval-actions\");\n  let popover: PopoverHandle | null = null;\n\n  const applyPrimaryColors = (el: HTMLElement): void => {\n    if (approvalConfig?.approveButtonColor) el.style.background = approvalConfig.approveButtonColor;\n    if (approvalConfig?.approveButtonTextColor) el.style.color = approvalConfig.approveButtonTextColor;\n  };\n  const denyBtn = createElement(\"button\", \"persona-approval-deny\") as HTMLButtonElement;\n  denyBtn.type = \"button\";\n  denyBtn.setAttribute(\"data-action\", \"deny\");\n  if (approvalConfig?.denyButtonColor) denyBtn.style.background = approvalConfig.denyButtonColor;\n  if (approvalConfig?.denyButtonTextColor) denyBtn.style.color = approvalConfig.denyButtonTextColor;\n  denyBtn.append(approvalConfig?.denyLabel ?? \"Deny\");\n\n  if (enableAlways) {\n    const split = createElement(\"div\", \"persona-approval-split\");\n    const primary = createElement(\"button\", \"persona-approval-primary\") as HTMLButtonElement;\n    primary.type = \"button\";\n    primary.setAttribute(\"data-action\", \"always\");\n    applyPrimaryColors(primary);\n    primary.append(approvalConfig?.approveLabel ?? \"Always allow\", kbd(\"⏎\"));\n\n    const caret = createElement(\"button\", \"persona-approval-caret\") as HTMLButtonElement;\n    caret.type = \"button\";\n    caret.setAttribute(\"data-action\", \"toggle-menu\");\n    caret.setAttribute(\"aria-label\", \"More options\");\n    applyPrimaryColors(caret);\n    const caretIcon = renderLucideIcon(\"chevron-down\", 15, \"currentColor\", 2);\n    if (caretIcon) caret.appendChild(caretIcon);\n\n    split.append(primary, caret);\n    actions.append(split, denyBtn);\n    denyBtn.append(kbd(\"Esc\"));\n\n    // \"Allow once\" menu, portaled out of the transcript by createPopover so it\n    // overlays the rest of the UI and isn't clipped by the scroll container.\n    const menu = createElement(\"div\", \"persona-approval-menu\");\n    const once = createElement(\"button\", \"persona-approval-menu-item\") as HTMLButtonElement;\n    once.type = \"button\";\n    once.append(\"Allow once\", kbd(\"⌘⏎\"));\n    menu.appendChild(once);\n    popover = createPopover({\n      anchor: split,\n      content: menu,\n      placement: \"bottom-start\",\n      matchAnchorWidth: true,\n    });\n    state.popovers.set(message.id, popover);\n    once.addEventListener(\"click\", () => {\n      teardownMessage(state, message.id);\n      approve(); // Allow once\n    });\n  } else {\n    const allow = createElement(\n      \"button\",\n      \"persona-approval-primary persona-approval-primary--solo\"\n    ) as HTMLButtonElement;\n    allow.type = \"button\";\n    allow.setAttribute(\"data-action\", \"allow\");\n    applyPrimaryColors(allow);\n    allow.append(approvalConfig?.approveLabel ?? \"Allow\");\n    actions.append(allow, denyBtn);\n  }\n\n  body.appendChild(actions);\n  card.appendChild(body);\n\n  // Single delegated click listener; survives morph via the plugin hydrate path.\n  card.addEventListener(\"click\", (e) => {\n    const target = e.target instanceof Element ? e.target.closest(\"[data-action]\") : null;\n    if (!target) return;\n    const action = target.getAttribute(\"data-action\");\n    if (action === \"toggle-params\") {\n      const pre = card.querySelector<HTMLElement>('[data-role=\"params\"]');\n      if (pre) {\n        const willOpen = pre.hidden;\n        pre.hidden = !willOpen;\n        head.setAttribute(\"aria-expanded\", willOpen ? \"true\" : \"false\");\n        head.setAttribute(\"aria-label\", willOpen ? hideDetailsLabel : showDetailsLabel);\n        approvalDetailsExpansionState.set(message.id, willOpen);\n      }\n      return;\n    }\n    if (action === \"toggle-menu\") {\n      popover?.toggle();\n      return;\n    }\n    if (action === \"always\") {\n      teardownMessage(state, message.id);\n      approve({ remember: true });\n      return;\n    }\n    if (action === \"allow\") {\n      teardownMessage(state, message.id);\n      approve();\n      return;\n    }\n    if (action === \"deny\") {\n      teardownMessage(state, message.id);\n      deny();\n      return;\n    }\n  });\n\n  return card;\n};\n\n/**\n * The built-in approval renderer, shaped as a plugin so ui.ts can run it through\n * the same `renderApproval` pipeline as user plugins (which still take\n * precedence). Reads `config` from the render context, so a single plugin\n * serves every render for one widget instance.\n *\n * Returns the plugin alongside a `teardown` the host pushes into its destroy\n * callbacks — both close over the SAME per-instance state, so destroying one\n * widget never disturbs another widget's open approvals on the same page.\n */\nexport const createBuiltInApprovalPlugin = (): {\n  plugin: AgentWidgetPlugin;\n  teardown: () => void;\n} => {\n  const state = createInstanceState();\n\n  const plugin: AgentWidgetPlugin = {\n    id: \"persona-built-in-approval\",\n    renderApproval: ({ message, approve, deny, config }) => {\n      const approval = message?.approval;\n      if (!approval) return null;\n      const approvalConfig = resolveApprovalConfig(config);\n\n      if (approval.status !== \"pending\") {\n        teardownMessage(state, message.id);\n        // Approved → render nothing; the tool call takes over the transcript.\n        // (An empty hidden element, not null, suppresses the legacy fallback.)\n        if (approval.status === \"approved\") {\n          const hidden = document.createElement(\"div\");\n          hidden.style.display = \"none\";\n          return hidden;\n        }\n        return buildResolvedTrace(approval);\n      }\n\n      // Rebuild: drop any prior listener/popover before (re)binding fresh\n      // closures, but KEEP this approval's place in the pending order so a\n      // re-render of an older card doesn't reorder it ahead of a newer one.\n      detachMessage(state, message.id);\n      const enableAlways = approvalConfig?.enableAlwaysAllow === true;\n      const card = buildPending(state, message, approval, approvalConfig, approve, deny, enableAlways);\n\n      if (enableAlways) {\n        if (!state.pendingOrder.includes(message.id)) state.pendingOrder.push(message.id);\n        // The newest first-seen pending approval owns the shortcuts, regardless\n        // of which card happens to be re-rendering right now.\n        state.latestPendingApprovalId = state.pendingOrder[state.pendingOrder.length - 1];\n        const onKeydown = (e: KeyboardEvent): void => {\n          if (isEditableEventTarget(e)) return;\n          if (message.id !== state.latestPendingApprovalId) return;\n          if (e.key !== \"Escape\" && e.key !== \"Enter\") return;\n          e.preventDefault();\n          // Resolving here promotes the next-newest pending approval to owner.\n          // Stop immediate propagation so the SAME keypress doesn't then reach\n          // that freshly-promoted card's listener and resolve it too — one\n          // keypress resolves exactly one approval; the next owner waits for the\n          // next press.\n          e.stopImmediatePropagation();\n          teardownMessage(state, message.id);\n          if (e.key === \"Escape\") {\n            deny();\n          } else if (e.metaKey || e.ctrlKey) {\n            approve(); // Allow once\n          } else {\n            approve({ remember: true }); // Always allow\n          }\n        };\n        state.keyHandlers.set(message.id, onKeydown);\n        document.addEventListener(\"keydown\", onKeydown);\n      }\n\n      return card;\n    },\n  };\n\n  // Release every pending approval's global listener + popover for THIS widget.\n  const teardown = (): void => {\n    for (const id of [...state.keyHandlers.keys(), ...state.popovers.keys()]) {\n      teardownMessage(state, id);\n    }\n    state.latestPendingApprovalId = null;\n  };\n\n  return { plugin, teardown };\n};\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetSession } from \"../session\";\nimport { AgentWidgetMessage, AgentWidgetSuggestionChipsConfig } from \"../types\";\n\nexport interface SuggestionButtons {\n  buttons: HTMLButtonElement[];\n  render: (\n    chips: string[] | undefined,\n    session: AgentWidgetSession,\n    textarea: HTMLTextAreaElement,\n    messages?: AgentWidgetMessage[],\n    config?: AgentWidgetSuggestionChipsConfig,\n    opts?: SuggestionRenderOptions\n  ) => void;\n}\n\nexport interface SuggestionRenderOptions {\n  /**\n   * Chips pushed by the agent's `suggest_replies` tool rather than the\n   * static `suggestionChips` config. Skips the before-first-user-message\n   * gate (the caller already applied the latest-turn visibility rule) and\n   * dispatches `persona:suggestReplies:*` DOM events.\n   */\n  agentPushed?: boolean;\n}\n\nexport const createSuggestions = (container: HTMLElement): SuggestionButtons => {\n  const suggestionButtons: HTMLButtonElement[] = [];\n  // render() runs on every message change; only announce agent-pushed chips\n  // when the visible set actually changes, not on each re-render pass.\n  let lastAgentShownKey: string | null = null;\n\n  const render = (\n    chips: string[] | undefined,\n    session: AgentWidgetSession,\n    textarea: HTMLTextAreaElement,\n    messages?: AgentWidgetMessage[],\n    chipsConfig?: AgentWidgetSuggestionChipsConfig,\n    opts?: SuggestionRenderOptions\n  ) => {\n    container.innerHTML = \"\";\n    suggestionButtons.length = 0;\n    const agentPushed = opts?.agentPushed === true;\n    if (!agentPushed) lastAgentShownKey = null;\n    if (!chips || !chips.length) return;\n\n    // Hide config suggestions after the first user message is sent.\n    // Agent-pushed chips skip this gate: their visibility is the caller's\n    // latest-turn rule (last suggest_replies call with no user message after).\n    // Use provided messages or get from session\n    if (!agentPushed) {\n      const messagesToCheck = messages ?? (session ? session.getMessages() : []);\n      const hasUserMessage = messagesToCheck.some((msg) => msg.role === \"user\");\n      if (hasUserMessage) return;\n    }\n\n    const fragment = document.createDocumentFragment();\n    const streaming = session ? session.isStreaming() : false;\n\n    // Get font family mapping function\n    const getFontFamilyValue = (family: \"sans-serif\" | \"serif\" | \"mono\"): string => {\n      switch (family) {\n        case \"serif\":\n          return 'Georgia, \"Times New Roman\", Times, serif';\n        case \"mono\":\n          return '\"Courier New\", Courier, \"Lucida Console\", Monaco, monospace';\n        case \"sans-serif\":\n        default:\n          return '-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif';\n      }\n    };\n\n    chips.forEach((chip) => {\n      const btn = createElement(\n        \"button\",\n        \"persona-rounded-button persona-bg-persona-surface persona-px-3 persona-py-1.5 persona-text-xs persona-font-medium persona-text-persona-primary hover:persona-opacity-80 persona-cursor-pointer persona-border persona-border-persona-border\"\n      ) as HTMLButtonElement;\n      btn.type = \"button\";\n      btn.textContent = chip;\n      btn.disabled = streaming;\n\n      // Apply typography settings\n      if (chipsConfig?.fontFamily) {\n        btn.style.fontFamily = getFontFamilyValue(chipsConfig.fontFamily);\n      }\n      if (chipsConfig?.fontWeight) {\n        btn.style.fontWeight = chipsConfig.fontWeight;\n      }\n\n      // Apply padding settings\n      if (chipsConfig?.paddingX) {\n        btn.style.paddingLeft = chipsConfig.paddingX;\n        btn.style.paddingRight = chipsConfig.paddingX;\n      }\n      if (chipsConfig?.paddingY) {\n        btn.style.paddingTop = chipsConfig.paddingY;\n        btn.style.paddingBottom = chipsConfig.paddingY;\n      }\n\n      btn.addEventListener(\"click\", () => {\n        if (!session || session.isStreaming()) return;\n        textarea.value = \"\";\n        if (agentPushed) {\n          container.dispatchEvent(\n            new CustomEvent(\"persona:suggestReplies:selected\", {\n              detail: { suggestion: chip },\n              bubbles: true,\n              composed: true,\n            })\n          );\n        }\n        session.sendMessage(chip);\n      });\n      fragment.appendChild(btn);\n      suggestionButtons.push(btn);\n    });\n    container.appendChild(fragment);\n    if (agentPushed) {\n      const shownKey = JSON.stringify(chips);\n      if (shownKey !== lastAgentShownKey) {\n        lastAgentShownKey = shownKey;\n        container.dispatchEvent(\n          new CustomEvent(\"persona:suggestReplies:shown\", {\n            detail: { suggestions: [...chips] },\n            bubbles: true,\n            composed: true,\n          })\n        );\n      }\n    }\n  };\n\n  return {\n    buttons: suggestionButtons,\n    render\n  };\n};\n\n\n\n","import type { SSEEventRecord } from \"../types\";\nimport type { EventStreamStore } from \"./event-stream-store\";\n\nexport class EventStreamBuffer {\n  private buffer: SSEEventRecord[];\n  private head = 0;\n  private count = 0;\n  private totalCaptured = 0;\n  private eventTypesSet = new Set<string>();\n  private readonly maxSize: number;\n  private readonly store: EventStreamStore | null;\n\n  constructor(maxSize = 2000, store: EventStreamStore | null = null) {\n    this.maxSize = maxSize;\n    this.buffer = new Array(maxSize);\n    this.store = store;\n  }\n\n  push(event: SSEEventRecord): void {\n    this.buffer[this.head] = event;\n    this.head = (this.head + 1) % this.maxSize;\n    if (this.count < this.maxSize) {\n      this.count++;\n    }\n    this.totalCaptured++;\n    this.eventTypesSet.add(event.type);\n    this.store?.put(event);\n  }\n\n  getAll(): SSEEventRecord[] {\n    if (this.count === 0) return [];\n    if (this.count < this.maxSize) {\n      return this.buffer.slice(0, this.count);\n    }\n    // Buffer is full, items wrap around\n    return [\n      ...this.buffer.slice(this.head, this.maxSize),\n      ...this.buffer.slice(0, this.head)\n    ];\n  }\n\n  async restore(): Promise<number> {\n    if (!this.store) return 0;\n    const events = await this.store.getAll();\n    if (events.length === 0) return 0;\n\n    // Take the most recent maxSize events (store returns sorted by timestamp)\n    const toLoad = events.length > this.maxSize\n      ? events.slice(events.length - this.maxSize)\n      : events;\n\n    // Populate the ring buffer without writing back to the store\n    for (const event of toLoad) {\n      this.buffer[this.head] = event;\n      this.head = (this.head + 1) % this.maxSize;\n      if (this.count < this.maxSize) {\n        this.count++;\n      }\n      this.eventTypesSet.add(event.type);\n    }\n    this.totalCaptured = events.length;\n\n    return toLoad.length;\n  }\n\n  getAllFromStore(): Promise<SSEEventRecord[]> {\n    if (this.store) {\n      return this.store.getAll();\n    }\n    return Promise.resolve(this.getAll());\n  }\n\n  getRecent(count: number): SSEEventRecord[] {\n    const all = this.getAll();\n    if (count >= all.length) return all;\n    return all.slice(all.length - count);\n  }\n\n  getSize(): number {\n    return this.count;\n  }\n\n  getTotalCaptured(): number {\n    return this.totalCaptured;\n  }\n\n  getEvictedCount(): number {\n    return this.totalCaptured - this.count;\n  }\n\n  clear(): void {\n    this.buffer = new Array(this.maxSize);\n    this.head = 0;\n    this.count = 0;\n    this.totalCaptured = 0;\n    this.eventTypesSet.clear();\n    this.store?.clear();\n  }\n\n  destroy(): void {\n    this.buffer = [];\n    this.head = 0;\n    this.count = 0;\n    this.totalCaptured = 0;\n    this.eventTypesSet.clear();\n    this.store?.destroy();\n  }\n\n  getEventTypes(): string[] {\n    return Array.from(this.eventTypesSet);\n  }\n}\n","import type { SSEEventRecord } from \"../types\";\n\nexport class EventStreamStore {\n  private db: IDBDatabase | null = null;\n  private pendingWrites: SSEEventRecord[] = [];\n  private flushScheduled = false;\n  private isDestroyed = false;\n  private readonly dbName: string;\n  private readonly storeName: string;\n\n  constructor(dbName = \"persona-event-stream\", storeName = \"events\") {\n    this.dbName = dbName;\n    this.storeName = storeName;\n  }\n\n  open(): Promise<void> {\n    return new Promise((resolve, reject) => {\n      try {\n        const request = indexedDB.open(this.dbName, 1);\n\n        request.onupgradeneeded = () => {\n          const db = request.result;\n          if (!db.objectStoreNames.contains(this.storeName)) {\n            const store = db.createObjectStore(this.storeName, { keyPath: \"id\" });\n            store.createIndex(\"timestamp\", \"timestamp\", { unique: false });\n          }\n        };\n\n        request.onsuccess = () => {\n          this.db = request.result;\n          resolve();\n        };\n\n        request.onerror = () => {\n          reject(request.error);\n        };\n      } catch (err) {\n        reject(err);\n      }\n    });\n  }\n\n  put(event: SSEEventRecord): void {\n    if (!this.db || this.isDestroyed) return;\n    this.pendingWrites.push(event);\n    if (!this.flushScheduled) {\n      this.flushScheduled = true;\n      queueMicrotask(() => this.flushWrites());\n    }\n  }\n\n  putBatch(events: SSEEventRecord[]): void {\n    if (!this.db || this.isDestroyed || events.length === 0) return;\n    try {\n      const tx = this.db.transaction(this.storeName, \"readwrite\");\n      const store = tx.objectStore(this.storeName);\n      for (const event of events) {\n        store.put(event);\n      }\n    } catch {\n      // Silently fail - IndexedDB writes are best-effort\n    }\n  }\n\n  getAll(): Promise<SSEEventRecord[]> {\n    return new Promise((resolve, reject) => {\n      if (!this.db) {\n        resolve([]);\n        return;\n      }\n      try {\n        const tx = this.db.transaction(this.storeName, \"readonly\");\n        const store = tx.objectStore(this.storeName);\n        const index = store.index(\"timestamp\");\n        const request = index.getAll();\n\n        request.onsuccess = () => {\n          resolve(request.result as SSEEventRecord[]);\n        };\n\n        request.onerror = () => {\n          reject(request.error);\n        };\n      } catch (err) {\n        reject(err);\n      }\n    });\n  }\n\n  getCount(): Promise<number> {\n    return new Promise((resolve, reject) => {\n      if (!this.db) {\n        resolve(0);\n        return;\n      }\n      try {\n        const tx = this.db.transaction(this.storeName, \"readonly\");\n        const store = tx.objectStore(this.storeName);\n        const request = store.count();\n\n        request.onsuccess = () => {\n          resolve(request.result);\n        };\n\n        request.onerror = () => {\n          reject(request.error);\n        };\n      } catch (err) {\n        reject(err);\n      }\n    });\n  }\n\n  clear(): Promise<void> {\n    return new Promise((resolve, reject) => {\n      if (!this.db) {\n        resolve();\n        return;\n      }\n      this.pendingWrites = [];\n      try {\n        const tx = this.db.transaction(this.storeName, \"readwrite\");\n        const store = tx.objectStore(this.storeName);\n        const request = store.clear();\n\n        request.onsuccess = () => {\n          resolve();\n        };\n\n        request.onerror = () => {\n          reject(request.error);\n        };\n      } catch (err) {\n        reject(err);\n      }\n    });\n  }\n\n  close(): void {\n    if (this.db) {\n      this.db.close();\n      this.db = null;\n    }\n  }\n\n  destroy(): Promise<void> {\n    this.isDestroyed = true;\n    this.pendingWrites = [];\n    this.close();\n    return new Promise((resolve, reject) => {\n      try {\n        const request = indexedDB.deleteDatabase(this.dbName);\n\n        request.onsuccess = () => {\n          resolve();\n        };\n\n        request.onerror = () => {\n          reject(request.error);\n        };\n      } catch (err) {\n        reject(err);\n      }\n    });\n  }\n\n  private flushWrites(): void {\n    this.flushScheduled = false;\n    if (!this.db || this.isDestroyed || this.pendingWrites.length === 0) return;\n    const toWrite = this.pendingWrites;\n    this.pendingWrites = [];\n    try {\n      const tx = this.db.transaction(this.storeName, \"readwrite\");\n      const store = tx.objectStore(this.storeName);\n      for (const event of toWrite) {\n        store.put(event);\n      }\n    } catch {\n      // Silently fail - IndexedDB writes are best-effort\n    }\n  }\n}\n","// ============================================================================\n// Output Throughput Tracker\n// ============================================================================\n//\n// Derives an output tokens-per-second metric from the widget's existing SSE\n// event stream, for display in the Events diagnostics screen. This is a passive\n// consumer: it never mutates dispatch payloads, never forces debug mode, and\n// never changes the wire contract: it only inspects the `(type, payload)`\n// events that already flow through the SSE tap.\n//\n// Throughput is estimated live from visible text deltas while a run streams,\n// then prefers exact provider usage (output tokens) when terminal events carry\n// it. A run starts when the stream starts (or lazily on the first visible\n// delta), stays \"running\" across intermediate step/turn completions, and only\n// finalizes on terminal `flow_complete` / `agent_complete`. Stream errors mark\n// the metric unavailable rather than leaving it stuck \"running\".\n\nexport type ThroughputMetricStatus = \"idle\" | \"running\" | \"complete\" | \"error\";\n\nexport type ThroughputMetricSource = \"usage\" | \"estimate\";\n\nexport interface ThroughputMetric {\n  status: ThroughputMetricStatus;\n  /** Output tokens per second, when computable. */\n  tokensPerSecond?: number;\n  /** Output tokens counted/estimated for the run. */\n  outputTokens?: number;\n  /** Duration window the rate was computed over (ms). */\n  durationMs?: number;\n  /** Whether `outputTokens` came from provider usage or a text estimate. */\n  source?: ThroughputMetricSource;\n}\n\ninterface ThroughputRunStats {\n  startedAt: number;\n  firstDeltaAt?: number;\n  /**\n   * Running character count of accumulated visible text, estimated via the\n   * ~4 chars/token heuristic. Tracked as a counter (not the concatenated\n   * string) so the estimate stays O(1) per delta over a long stream.\n   */\n  visibleCharCount: number;\n  exactOutputTokens: number;\n}\n\n// Below this streamed window we don't trust the rate; fall back to provider\n// execution time or whole-request duration instead.\nconst THROUGHPUT_MIN_DURATION_MS = 250;\n\n// Request-level lifecycle events: each marks the beginning of a NEW request.\n// The SSE tap fires for every payload type regardless of whether the client has\n// a handler for it, so any of these that the server emits starts the run with\n// an accurate `startedAt` (capturing time-to-first-token). These RESET any run\n// already in progress, so a prior stream that ended without a terminal/error\n// frame (e.g. `session.cancel()`) doesn't bleed its tokens into the next one.\nconst REQUEST_START_EVENTS = new Set([\n  \"flow_start\",\n  \"flow_run_start\",\n  \"agent_start\",\n  \"dispatch_start\",\n  \"run_start\",\n]);\n\n// Per-step markers that fire repeatedly WITHIN a single request (a flow emits\n// one per step). These only lazily begin a run: they must never reset, or a\n// multi-step response would restart the metric between steps. If no request- or\n// step-start event is emitted, the first visible delta lazily starts the run.\nconst STEP_START_EVENTS = new Set([\"step_start\", \"execution_start\"]);\n\nconst VISIBLE_DELTA_EVENTS = new Set([\n  \"step_delta\",\n  \"step_chunk\",\n  \"chunk\",\n  \"agent_turn_delta\",\n]);\n\nconst INTERMEDIATE_COMPLETE_EVENTS = new Set([\n  \"step_complete\",\n  \"agent_turn_complete\",\n]);\n\nconst TERMINAL_COMPLETE_EVENTS = new Set([\"flow_complete\", \"agent_complete\"]);\n\nconst ERROR_EVENTS = new Set([\n  \"step_error\",\n  \"flow_error\",\n  \"agent_error\",\n  \"dispatch_error\",\n  \"error\",\n]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n  typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst toFiniteNumber = (value: unknown): number | undefined =>\n  typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n\nconst getRecord = (\n  value: Record<string, unknown>,\n  key: string\n): Record<string, unknown> | undefined => {\n  const nested = value[key];\n  return isRecord(nested) ? nested : undefined;\n};\n\n/** Token estimate from a character count: ~4 chars/token, floor of 1. */\nfunction estimateTokensFromCharCount(charCount: number): number {\n  return charCount > 0 ? Math.max(1, Math.ceil(charCount / 4)) : 0;\n}\n\n/** Simple token estimate matching the dashboard heuristic: ~4 chars/token. */\nexport function estimateOutputTokens(text: string): number {\n  return estimateTokensFromCharCount(text.trim().length);\n}\n\nfunction calculateTokensPerSecond(\n  outputTokens: number,\n  durationMs: number | undefined\n): number | undefined {\n  if (\n    outputTokens <= 0 ||\n    durationMs === undefined ||\n    durationMs < THROUGHPUT_MIN_DURATION_MS\n  ) {\n    return undefined;\n  }\n  return outputTokens / (durationMs / 1000);\n}\n\nfunction resolveEventType(\n  eventType: string,\n  payload: Record<string, unknown>\n): string {\n  return typeof payload.type === \"string\" ? payload.type : eventType;\n}\n\nfunction getTextDelta(payload: Record<string, unknown>): string {\n  if (typeof payload.text === \"string\") return payload.text;\n  if (typeof payload.delta === \"string\") return payload.delta;\n  if (typeof payload.content === \"string\") return payload.content;\n  if (typeof payload.chunk === \"string\") return payload.chunk;\n  return \"\";\n}\n\n/**\n * Only count visible model output.\n *\n * For `agent_turn_delta`, count only a contentType of exactly `text` as\n * visible, matching the client renderer (which appends streaming assistant\n * text only when `contentType === \"text\"`); `thinking`, `tool_input`, any\n * other value, and a missing contentType are ignored so throughput never\n * includes deltas the chat UI doesn't render.\n *\n * For `step_delta` / `step_chunk`, skip tool and context steps: those carry\n * tool I/O, not model-visible text: mirroring the widget's own renderer.\n */\nfunction isVisibleTextDelta(\n  type: string,\n  payload: Record<string, unknown>\n): boolean {\n  if (type === \"step_delta\" || type === \"step_chunk\") {\n    return payload.stepType !== \"tool\" && payload.executionType !== \"context\";\n  }\n\n  if (type !== \"agent_turn_delta\") return true;\n\n  const contentType =\n    typeof payload.contentType === \"string\"\n      ? payload.contentType\n      : typeof payload.content_type === \"string\"\n        ? payload.content_type\n        : undefined;\n\n  return contentType === \"text\";\n}\n\n/** Extract exact output tokens from a variety of usage payload shapes. */\nfunction getOutputTokens(payload: Record<string, unknown>): number | undefined {\n  const result = getRecord(payload, \"result\");\n  const candidates = [\n    getRecord(payload, \"tokens\"),\n    getRecord(payload, \"totalTokens\"),\n    result ? getRecord(result, \"tokens\") : undefined,\n    getRecord(payload, \"usage\"),\n    result ? getRecord(result, \"usage\") : undefined,\n  ];\n\n  for (const candidate of candidates) {\n    if (!candidate) continue;\n    const outputTokens =\n      toFiniteNumber(candidate.output) ??\n      toFiniteNumber(candidate.outputTokens) ??\n      toFiniteNumber(candidate.completionTokens);\n    if (outputTokens !== undefined) return outputTokens;\n  }\n\n  return (\n    toFiniteNumber(payload.outputTokens) ??\n    toFiniteNumber(payload.completionTokens) ??\n    (result\n      ? (toFiniteNumber(result.outputTokens) ??\n        toFiniteNumber(result.completionTokens))\n      : undefined)\n  );\n}\n\n/** Extract provider execution time (ms) from a variety of payload shapes. */\nfunction getExecutionTimeMs(\n  payload: Record<string, unknown>\n): number | undefined {\n  const result = getRecord(payload, \"result\");\n  return (\n    toFiniteNumber(payload.executionTime) ??\n    toFiniteNumber(payload.executionTimeMs) ??\n    toFiniteNumber(payload.execution_time) ??\n    toFiniteNumber(payload.duration) ??\n    (result\n      ? (toFiniteNumber(result.executionTime) ??\n        toFiniteNumber(result.executionTimeMs))\n      : undefined)\n  );\n}\n\nfunction defaultClock(): number {\n  if (\n    typeof performance !== \"undefined\" &&\n    typeof performance.now === \"function\"\n  ) {\n    return performance.now();\n  }\n  return Date.now();\n}\n\n/**\n * Tracks output throughput across one streamed run at a time. Feed it every SSE\n * event via {@link processEvent}; read the current state via {@link getMetric}.\n */\nexport class ThroughputTracker {\n  private metric: ThroughputMetric = { status: \"idle\" };\n  private run: ThroughputRunStats | null = null;\n  private readonly now: () => number;\n\n  constructor(now: () => number = defaultClock) {\n    this.now = now;\n  }\n\n  getMetric(): ThroughputMetric {\n    // While a run is streaming, recompute the elapsed window (and rate) from the\n    // clock on each read. The view polls this every ~200ms, so without this a\n    // pause between deltas would keep showing the stale rate from the last\n    // event; recomputing lets the displayed tok/s decay as time passes.\n    const run = this.run;\n    if (\n      run &&\n      this.metric.status === \"running\" &&\n      run.firstDeltaAt !== undefined &&\n      this.metric.outputTokens !== undefined\n    ) {\n      const durationMs = this.now() - run.firstDeltaAt;\n      return {\n        ...this.metric,\n        durationMs,\n        tokensPerSecond: calculateTokensPerSecond(\n          this.metric.outputTokens,\n          durationMs\n        ),\n      };\n    }\n    return this.metric;\n  }\n\n  /** Reset back to idle (e.g. when the chat is cleared). */\n  reset(): void {\n    this.run = null;\n    this.metric = { status: \"idle\" };\n  }\n\n  private startRun(now: number): void {\n    this.run = {\n      startedAt: now,\n      visibleCharCount: 0,\n      exactOutputTokens: 0,\n    };\n    this.metric = { status: \"running\" };\n  }\n\n  processEvent(eventType: string, payload: unknown): void {\n    if (!isRecord(payload)) {\n      // Non-object payloads can still signal lifecycle (e.g. bare \"error\").\n      if (ERROR_EVENTS.has(eventType) && this.run) {\n        this.run = null;\n        this.metric = { status: \"error\" };\n      }\n      return;\n    }\n\n    const type = resolveEventType(eventType, payload);\n    const now = this.now();\n\n    if (REQUEST_START_EVENTS.has(type)) {\n      // New request: start fresh, discarding any incomplete prior run.\n      this.startRun(now);\n      return;\n    }\n\n    if (STEP_START_EVENTS.has(type)) {\n      // Mid-request step marker: only begin a run if none is active.\n      if (!this.run) this.startRun(now);\n      return;\n    }\n\n    if (VISIBLE_DELTA_EVENTS.has(type)) {\n      if (!isVisibleTextDelta(type, payload)) return;\n      const text = getTextDelta(payload);\n      if (!text) return;\n\n      // Lazily start a run if the stream began without a recognized start event.\n      if (!this.run) this.startRun(now);\n      const stats = this.run!;\n\n      stats.firstDeltaAt ??= now;\n      stats.visibleCharCount += text.length;\n\n      // Add the live char estimate of the CURRENT (not-yet-completed) step on\n      // top of any exact usage already booked from completed steps, so the\n      // count only grows: it never drops back to a bare estimate mid-run.\n      const outputTokens =\n        stats.exactOutputTokens +\n        estimateTokensFromCharCount(stats.visibleCharCount);\n      const durationMs = now - stats.firstDeltaAt;\n      this.metric = {\n        status: \"running\",\n        tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),\n        outputTokens,\n        durationMs,\n        source: stats.exactOutputTokens > 0 ? \"usage\" : \"estimate\",\n      };\n      return;\n    }\n\n    if (INTERMEDIATE_COMPLETE_EVENTS.has(type)) {\n      // Accumulate exact usage but keep the run going: these fire per\n      // step/turn, not at the end of the whole run.\n      if (!this.run) return;\n      const stats = this.run;\n      const exact = getOutputTokens(payload);\n      if (exact !== undefined) {\n        stats.exactOutputTokens += exact;\n        // This step's visible text is now represented exactly by provider\n        // usage: drop it from the running char estimate so the two don't\n        // double-count once the next step starts streaming.\n        stats.visibleCharCount = 0;\n      }\n\n      const usingExact = stats.exactOutputTokens > 0;\n      const outputTokens =\n        stats.exactOutputTokens +\n        estimateTokensFromCharCount(stats.visibleCharCount);\n      const durationMs = this.resolveDuration(stats, payload, now);\n      this.metric = {\n        status: \"running\",\n        tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),\n        outputTokens,\n        durationMs,\n        source: usingExact ? \"usage\" : \"estimate\",\n      };\n      return;\n    }\n\n    if (TERMINAL_COMPLETE_EVENTS.has(type)) {\n      if (!this.run) return;\n      const stats = this.run;\n      // Prefer exact output tokens from this terminal event, else accumulated\n      // usage from intermediate completes, else the text estimate.\n      const terminalExact = getOutputTokens(payload);\n      // Prefer a total from the terminal event; otherwise sum exact usage booked\n      // from intermediate completes plus the estimate of any still-streamed text\n      // not yet covered by a usage report.\n      const outputTokens =\n        terminalExact ??\n        stats.exactOutputTokens +\n          estimateTokensFromCharCount(stats.visibleCharCount);\n      const source: ThroughputMetricSource =\n        terminalExact !== undefined || stats.exactOutputTokens > 0\n          ? \"usage\"\n          : \"estimate\";\n      const durationMs = this.resolveDuration(stats, payload, now);\n      this.metric = {\n        status: \"complete\",\n        tokensPerSecond: calculateTokensPerSecond(outputTokens, durationMs),\n        outputTokens,\n        durationMs,\n        source,\n      };\n      this.run = null;\n      return;\n    }\n\n    if (ERROR_EVENTS.has(type)) {\n      if (!this.run) return;\n      this.run = null;\n      this.metric = { status: \"error\" };\n    }\n  }\n\n  /**\n   * Prefer the streamed visible-output window when it clears the minimum\n   * threshold; otherwise fall back to provider execution time, then to the\n   * whole-request duration.\n   */\n  private resolveDuration(\n    stats: ThroughputRunStats,\n    payload: Record<string, unknown>,\n    now: number\n  ): number {\n    const streamedDurationMs =\n      stats.firstDeltaAt !== undefined ? now - stats.firstDeltaAt : undefined;\n    if (\n      streamedDurationMs !== undefined &&\n      streamedDurationMs >= THROUGHPUT_MIN_DURATION_MS\n    ) {\n      return streamedDurationMs;\n    }\n    const executionTimeMs = getExecutionTimeMs(payload);\n    return executionTimeMs ?? now - stats.startedAt;\n  }\n}\n","import { createElement } from \"../utils/dom\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport {\n  createFollowStateController,\n  isElementNearBottom,\n  resolveFollowStateFromScroll,\n  resolveFollowStateFromWheel\n} from \"../utils/auto-follow\";\nimport type { EventStreamBuffer } from \"../utils/event-stream-buffer\";\nimport type { ThroughputMetric } from \"../utils/throughput-tracker\";\nimport type {\n  SSEEventRecord,\n  AgentWidgetConfig,\n  EventStreamConfig,\n  EventStreamBadgeColor,\n} from \"../types\";\nimport type { AgentWidgetPlugin } from \"../plugins/types\";\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Append custom class names to an element if provided. */\nfunction applyCustomClasses(el: HTMLElement, classes?: string): void {\n  if (classes) {\n    classes.split(/\\s+/).forEach((c) => c && el.classList.add(c));\n  }\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_BADGE_COLORS: Record<string, EventStreamBadgeColor> = {\n  flow_: { bg: \"var(--persona-palette-colors-success-100, #dcfce7)\", text: \"var(--persona-palette-colors-success-700, #166534)\" },\n  step_: { bg: \"var(--persona-palette-colors-primary-100, #f5f5f5)\", text: \"var(--persona-palette-colors-primary-700, #0a0a0a)\" },\n  reason_: { bg: \"var(--persona-palette-colors-warning-100, #ffedd5)\", text: \"var(--persona-palette-colors-warning-700, #9a3412)\" },\n  tool_: { bg: \"var(--persona-palette-colors-purple-100, #f3e8ff)\", text: \"var(--persona-palette-colors-purple-700, #6b21a8)\" },\n  agent_: { bg: \"var(--persona-palette-colors-teal-100, #ccfbf1)\", text: \"var(--persona-palette-colors-teal-700, #115e59)\" },\n  error: { bg: \"var(--persona-palette-colors-error-100, #fecaca)\", text: \"var(--persona-palette-colors-error-700, #991b1b)\" },\n};\nconst DEFAULT_BADGE_COLOR: EventStreamBadgeColor = {\n  bg: \"var(--persona-palette-colors-gray-100, #f3f4f6)\",\n  text: \"var(--persona-palette-colors-gray-600, #4b5563)\",\n};\n\nconst DEFAULT_DESCRIPTION_FIELDS = [\n  \"flowName\",\n  \"stepName\",\n  \"reasoningText\",\n  \"text\",\n  \"name\",\n  \"tool\",\n  \"toolName\",\n];\n\n// Minimum interval between renders (ms)\nconst UPDATE_THROTTLE_MS = 100;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction getBadgeColor(\n  eventType: string,\n  customColors?: Record<string, EventStreamBadgeColor>\n): EventStreamBadgeColor {\n  const allColors = { ...DEFAULT_BADGE_COLORS, ...customColors };\n  // Exact match first\n  if (allColors[eventType]) return allColors[eventType];\n  // Prefix match (keys ending with \"_\")\n  for (const prefix of Object.keys(allColors)) {\n    if (prefix.endsWith(\"_\") && eventType.startsWith(prefix)) {\n      return allColors[prefix];\n    }\n  }\n  return DEFAULT_BADGE_COLOR;\n}\n\nfunction formatRelativeTimestamp(ms: number, firstEventMs: number): string {\n  const delta = (ms - firstEventMs) / 1000;\n  return `+${delta.toFixed(3)}s`;\n}\n\nfunction formatAbsoluteTimestamp(ms: number): string {\n  const d = new Date(ms);\n  const hh = String(d.getHours()).padStart(2, \"0\");\n  const mm = String(d.getMinutes()).padStart(2, \"0\");\n  const ss = String(d.getSeconds()).padStart(2, \"0\");\n  const mmm = String(d.getMilliseconds()).padStart(3, \"0\");\n  return `${hh}:${mm}:${ss}.${mmm}`;\n}\n\nfunction extractDescription(\n  payload: string,\n  fields: string[]\n): string | null {\n  try {\n    const obj = JSON.parse(payload) as Record<string, unknown>;\n    if (typeof obj !== \"object\" || obj === null) return null;\n    for (const field of fields) {\n      const parts = field.split(\".\");\n      let current: unknown = obj;\n      for (const part of parts) {\n        if (current && typeof current === \"object\" && current !== null) {\n          current = (current as Record<string, unknown>)[part];\n        } else {\n          current = undefined;\n          break;\n        }\n      }\n      if (typeof current === \"string\" && current.trim()) return current.trim();\n    }\n  } catch {\n    // Not JSON, no description\n  }\n  return null;\n}\n\nfunction copyToClipboard(text: string): Promise<void> {\n  if (navigator.clipboard?.writeText) {\n    return navigator.clipboard.writeText(text);\n  }\n  return new Promise((resolve) => {\n    const textarea = document.createElement(\"textarea\");\n    textarea.value = text;\n    textarea.style.position = \"fixed\";\n    textarea.style.opacity = \"0\";\n    document.body.appendChild(textarea);\n    textarea.select();\n    document.execCommand(\"copy\");\n    document.body.removeChild(textarea);\n    resolve();\n  });\n}\n\nfunction formatEventForCopy(event: SSEEventRecord): string {\n  let formattedPayload: unknown;\n  try {\n    formattedPayload = JSON.parse(event.payload);\n  } catch {\n    formattedPayload = event.payload;\n  }\n  return JSON.stringify(\n    {\n      type: event.type,\n      timestamp: new Date(event.timestamp).toISOString(),\n      payload: formattedPayload,\n    },\n    null,\n    2\n  );\n}\n\n// ============================================================================\n// Output Throughput Summary\n// ============================================================================\n\n/** Format the headline value, e.g. `23.9 tok/s` or `-- tok/s` when unavailable. */\nfunction formatThroughputValue(metric: ThroughputMetric): string {\n  if (\n    metric.tokensPerSecond === undefined ||\n    !Number.isFinite(metric.tokensPerSecond)\n  ) {\n    return \"-- tok/s\";\n  }\n  return `${metric.tokensPerSecond.toFixed(1)} tok/s`;\n}\n\n/** Compact supporting details: output tokens, duration, and usage/estimate source. */\nfunction formatThroughputMeta(metric: ThroughputMetric): string {\n  const parts: string[] = [];\n  if (metric.outputTokens !== undefined) {\n    parts.push(`${metric.outputTokens.toLocaleString()} tok`);\n  }\n  if (metric.durationMs !== undefined) {\n    parts.push(`${(metric.durationMs / 1000).toFixed(2)}s`);\n  }\n  if (metric.source) {\n    parts.push(metric.source);\n  }\n  return parts.join(\" · \");\n}\n\n// ============================================================================\n// Inline Payload Component\n// ============================================================================\n\nfunction renderInlinePayload(\n  event: SSEEventRecord,\n  plugins: AgentWidgetPlugin[],\n  config: AgentWidgetConfig | undefined\n): HTMLElement {\n  let formattedPayload: string;\n  let parsedPayload: unknown;\n  try {\n    parsedPayload = JSON.parse(event.payload);\n    formattedPayload = JSON.stringify(parsedPayload, null, 2);\n  } catch {\n    parsedPayload = event.payload;\n    formattedPayload = event.payload;\n  }\n\n  // Plugin hook: renderEventStreamPayload\n  const payloadPlugin = plugins.find((p) => p.renderEventStreamPayload);\n  if (payloadPlugin?.renderEventStreamPayload && config) {\n    const customPayload = payloadPlugin.renderEventStreamPayload({\n      event,\n      config,\n      defaultRenderer: () => renderDefaultPayload(),\n      parsedPayload,\n    });\n    if (customPayload) return customPayload;\n  }\n\n  return renderDefaultPayload();\n\n  function renderDefaultPayload(): HTMLElement {\n    const payloadContainer = createElement(\n      \"div\",\n      \"persona-bg-persona-container persona-border-t persona-border-persona-divider persona-px-3 persona-py-2 persona-ml-4 persona-mr-3 persona-mb-1 persona-rounded-b persona-overflow-auto persona-max-h-[300px]\"\n    );\n\n    const payloadContent = createElement(\n      \"pre\",\n      \"persona-m-0 persona-whitespace-pre-wrap persona-break-all persona-text-[11px] persona-text-persona-secondary persona-font-mono\"\n    );\n    payloadContent.textContent = formattedPayload;\n\n    payloadContainer.appendChild(payloadContent);\n    return payloadContainer;\n  }\n}\n\n// ============================================================================\n// Event Row Component\n// ============================================================================\n\nfunction renderEventRow(\n  event: SSEEventRecord,\n  index: number,\n  firstTimestamp: number,\n  esConfig: EventStreamConfig,\n  expandedSet: Set<string>,\n  onToggleExpand: (eventId: string) => void,\n  plugins: AgentWidgetPlugin[],\n  config: AgentWidgetConfig | undefined\n): HTMLElement {\n  const isExpanded = expandedSet.has(event.id);\n  const wrapper = createElement(\n    \"div\",\n    \"persona-border-b persona-border-persona-divider persona-text-xs\"\n  );\n  applyCustomClasses(wrapper, esConfig.classNames?.eventRow);\n\n  // Plugin hook: renderEventStreamRow\n  const rowPlugin = plugins.find((p) => p.renderEventStreamRow);\n  if (rowPlugin?.renderEventStreamRow && config) {\n    const customRow = rowPlugin.renderEventStreamRow({\n      event,\n      index,\n      config,\n      defaultRenderer: () => buildDefaultRowContent(),\n      isExpanded,\n      onToggleExpand: () => onToggleExpand(event.id),\n    });\n    if (customRow) {\n      wrapper.appendChild(customRow);\n      return wrapper;\n    }\n  }\n\n  wrapper.appendChild(buildDefaultRowContent());\n  return wrapper;\n\n  function buildDefaultRowContent(): HTMLElement {\n    const container = createElement(\"div\", \"\");\n\n    // Main row line\n    const row = createElement(\n      \"div\",\n      \"persona-flex persona-items-center persona-gap-2 persona-px-3 persona-py-3 hover:persona-bg-persona-container persona-cursor-pointer persona-group\"\n    );\n    row.setAttribute(\"data-event-id\", event.id);\n\n    // 1. Chevron (expand/collapse)\n    const chevron = createElement(\n      \"span\",\n      \"persona-flex-shrink-0 persona-text-persona-muted persona-w-4 persona-text-center persona-flex persona-items-center persona-justify-center\"\n    );\n    const chevronIcon = renderLucideIcon(\n      isExpanded ? \"chevron-down\" : \"chevron-right\",\n      \"14px\",\n      \"currentColor\",\n      2\n    );\n    if (chevronIcon) chevron.appendChild(chevronIcon);\n\n    // 2. Timestamp\n    const timestamp = createElement(\n      \"span\",\n      \"persona-text-[11px] persona-text-persona-muted persona-whitespace-nowrap persona-flex-shrink-0 persona-font-mono persona-w-[70px]\"\n    );\n    const tsFormat = esConfig.timestampFormat ?? \"relative\";\n    timestamp.textContent =\n      tsFormat === \"relative\"\n        ? formatRelativeTimestamp(event.timestamp, firstTimestamp)\n        : formatAbsoluteTimestamp(event.timestamp);\n\n    // 3. Sequence number\n    let seqNum: HTMLElement | null = null;\n    if (esConfig.showSequenceNumbers !== false) {\n      seqNum = createElement(\n        \"span\",\n        \"persona-text-[11px] persona-text-persona-muted persona-font-mono persona-flex-shrink-0 persona-w-[28px] persona-text-right\"\n      );\n      seqNum.textContent = String(index + 1);\n    }\n\n    // 4. Color-coded type badge\n    const badgeColor = getBadgeColor(event.type, esConfig.badgeColors);\n    const badge = createElement(\n      \"span\",\n      \"persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded persona-text-[11px] persona-font-mono persona-font-medium persona-whitespace-nowrap persona-flex-shrink-0 persona-border\"\n    );\n    badge.style.backgroundColor = badgeColor.bg;\n    badge.style.color = badgeColor.text;\n    badge.style.borderColor = badgeColor.text + \"50\";\n    badge.textContent = event.type;\n\n    // 5. Description (extracted from payload)\n    const descFields =\n      esConfig.descriptionFields ?? DEFAULT_DESCRIPTION_FIELDS;\n    const desc = extractDescription(event.payload, descFields);\n    let descEl: HTMLElement | null = null;\n    if (desc) {\n      descEl = createElement(\n        \"span\",\n        \"persona-text-[11px] persona-text-persona-secondary persona-truncate persona-min-w-0\"\n      );\n      descEl.textContent = desc;\n    }\n\n    // 6. Spacer\n    const spacer = createElement(\"div\", \"persona-flex-1 persona-min-w-0\");\n\n    // 7. Copy button\n    const copyBtn = createElement(\n      \"button\",\n      \"persona-text-persona-muted hover:persona-text-persona-primary persona-cursor-pointer persona-flex-shrink-0 persona-border-none persona-bg-transparent persona-p-0\"\n    );\n    const clipIcon = renderLucideIcon(\n      \"clipboard\",\n      \"12px\",\n      \"currentColor\",\n      1.5\n    );\n    if (clipIcon) copyBtn.appendChild(clipIcon);\n    copyBtn.addEventListener(\"click\", async (e: Event) => {\n      e.stopPropagation();\n      await copyToClipboard(formatEventForCopy(event));\n      // Visual feedback\n      copyBtn.innerHTML = \"\";\n      const checkIcon = renderLucideIcon(\n        \"check\",\n        \"12px\",\n        \"currentColor\",\n        1.5\n      );\n      if (checkIcon) copyBtn.appendChild(checkIcon);\n      setTimeout(() => {\n        copyBtn.innerHTML = \"\";\n        const restoreIcon = renderLucideIcon(\n          \"clipboard\",\n          \"12px\",\n          \"currentColor\",\n          1.5\n        );\n        if (restoreIcon) copyBtn.appendChild(restoreIcon);\n      }, 1500);\n    });\n\n    // Assemble row\n    row.appendChild(chevron);\n    row.appendChild(timestamp);\n    if (seqNum) row.appendChild(seqNum);\n    row.appendChild(badge);\n    if (descEl) row.appendChild(descEl);\n    row.appendChild(spacer);\n    row.appendChild(copyBtn);\n\n    container.appendChild(row);\n\n    // Expanded payload (inline)\n    if (isExpanded) {\n      container.appendChild(\n        renderInlinePayload(event, plugins, config)\n      );\n    }\n\n    return container;\n  }\n}\n\n// ============================================================================\n// Main View\n// ============================================================================\n\nexport type EventStreamViewOptions = {\n  buffer: EventStreamBuffer;\n  getFullHistory?: () => Promise<SSEEventRecord[]>;\n  onClose?: () => void;\n  config?: AgentWidgetConfig;\n  plugins?: AgentWidgetPlugin[];\n  /**\n   * Optional accessor for the current output-throughput metric, derived from\n   * the same SSE event stream. When provided, a compact \"Output throughput\"\n   * summary row is rendered and refreshed on each update.\n   */\n  getThroughput?: () => ThroughputMetric;\n};\n\nexport function createEventStreamView(\n  options: EventStreamViewOptions\n): {\n  element: HTMLElement;\n  update: () => void;\n  destroy: () => void;\n} {\n  const {\n    buffer,\n    getFullHistory,\n    onClose,\n    config,\n    plugins = [],\n    getThroughput,\n  } = options;\n  const scrollToBottomConfig = config?.features?.scrollToBottom;\n  const scrollToBottomEnabled = scrollToBottomConfig?.enabled !== false;\n  const scrollToBottomIconName = scrollToBottomConfig?.iconName ?? \"arrow-down\";\n  const scrollToBottomLabel = scrollToBottomConfig?.label ?? \"\";\n\n  const esConfig: EventStreamConfig = config?.features?.eventStream ?? {};\n\n  // --- Plugin hook: renderEventStreamView (replace entire view) ---\n  const viewPlugin = plugins.find((p) => p.renderEventStreamView);\n  if (viewPlugin?.renderEventStreamView && config) {\n    const customView = viewPlugin.renderEventStreamView({\n      config,\n      events: buffer.getAll(),\n      defaultRenderer: () => buildDefaultView().element,\n      onClose,\n    });\n    if (customView) {\n      return {\n        element: customView,\n        update: () => {\n          // Plugin manages its own updates\n        },\n        destroy: () => {\n          // Plugin manages its own cleanup\n        },\n      };\n    }\n  }\n\n  return buildDefaultView();\n\n  function buildDefaultView(): {\n    element: HTMLElement;\n    update: () => void;\n    destroy: () => void;\n  } {\n    const customClasses = esConfig.classNames;\n    const container = createElement(\n      \"div\",\n      \"persona-event-stream-view persona-flex persona-flex-col persona-flex-1 persona-min-h-0\"\n    );\n    applyCustomClasses(container, customClasses?.panel);\n\n    // State\n    let filteredEvents: SSEEventRecord[] = [];\n    let selectedType = \"\";\n    let searchTerm = \"\";\n    let searchTimeout: ReturnType<typeof setTimeout> | null = null;\n    let lastKnownTypes: string[] = [];\n    let lastTypeCounts: Record<string, number> = {};\n    let lastFilteredCount = 0;\n    const autoFollow = createFollowStateController();\n    let newEventsSincePause = 0;\n    let lastRenderTime = 0;\n    let pendingUpdate = false;\n    let pendingRafId: number | null = null;\n    let suppressScrollHandler = false;\n    let lastScrollTop = 0;\n    const expandedSet = new Set<string>();\n\n    // Incremental rendering state\n    const rowElements = new Map<string, HTMLElement>();\n    let lastRenderedFilter = \"\";\n    let lastRenderedSearch = \"\";\n    let dirtyExpandId: string | null = null;\n\n    // ========================================================================\n    // Toolbar: Header Bar + Search Bar\n    // ========================================================================\n\n    // Elements we need references to across functions\n    // These are assigned inside buildDefaultToolbar() which always runs\n    let filterSelect!: HTMLSelectElement;\n    let copyAllBtn!: HTMLButtonElement;\n    let searchInput!: HTMLInputElement;\n    let searchClearBtn!: HTMLButtonElement;\n    // Inline \"Throughput <tok/s>\" group, rendered into the header bar next to\n    // the \"Events\" count when getThroughput is provided. The detailed\n    // breakdown is revealed on hover via the native title tooltip.\n    let throughputValueEl: HTMLElement | null = null;\n    let throughputContainer: HTMLElement | null = null;\n    let throughputTooltipEl: HTMLElement | null = null;\n\n    function buildDefaultToolbar(): HTMLElement {\n      const toolbarOuter = createElement(\n        \"div\",\n        \"persona-event-toolbar persona-relative persona-flex persona-flex-col persona-flex-shrink-0\"\n      );\n\n      // --- Header bar ---\n      const headerBar = createElement(\n        \"div\",\n        \"persona-flex persona-items-center persona-gap-2 persona-px-4 persona-py-3 persona-pb-0 persona-border-persona-divider persona-bg-persona-surface persona-overflow-hidden\"\n      );\n      applyCustomClasses(headerBar, customClasses?.headerBar);\n\n      // The header leads straight into the controls. Context already names\n      // itself: the \"All events (N)\" filter option carries the total, the search\n      // placeholder names event payloads, and every row is an event.\n\n      // Inline throughput value, e.g. \"146.3 tok/s\". The \"tok/s\" unit is\n      // self-describing for this developer-facing inspector, so the value stands\n      // alone; its accessible name (\"Throughput: <value>\") and detailed breakdown\n      // live on the container's aria-label / hover tooltip (see updateThroughputSummary).\n      if (getThroughput) {\n        throughputContainer = createElement(\n          \"div\",\n          \"persona-relative persona-flex persona-items-center persona-gap-1.5 persona-whitespace-nowrap\"\n        );\n        throughputContainer.style.cursor = \"help\";\n        throughputValueEl = createElement(\n          \"span\",\n          \"persona-text-[11px] persona-font-mono persona-bg-persona-container persona-text-persona-muted persona-px-2 persona-py-0.5 persona-rounded persona-border persona-border-persona-border persona-tabular-nums\"\n        );\n        throughputValueEl.textContent = \"-- tok/s\";\n\n        // Custom hover tooltip: appears instantly (no native title delay).\n        // Appended to the (non-clipping) toolbar wrapper rather than the header\n        // bar, which has overflow-hidden and would clip a dropdown. Position is\n        // measured on hover so it sits just under the throughput group.\n        throughputTooltipEl = createElement(\n          \"div\",\n          \"persona-absolute persona-z-50 persona-whitespace-nowrap persona-rounded persona-border persona-border-persona-border persona-bg-persona-container persona-text-persona-primary persona-text-[11px] persona-font-mono persona-px-2 persona-py-1 persona-shadow\"\n        );\n        throughputTooltipEl.style.display = \"none\";\n        throughputTooltipEl.style.pointerEvents = \"none\";\n        const group = throughputContainer;\n        const tooltip = throughputTooltipEl;\n        const showTooltip = () => {\n          if (!tooltip.textContent) return;\n          const gRect = group.getBoundingClientRect();\n          const pRect = toolbarOuter.getBoundingClientRect();\n          tooltip.style.left = `${gRect.left - pRect.left}px`;\n          tooltip.style.top = `${gRect.bottom - pRect.top + 4}px`;\n          tooltip.style.display = \"block\";\n        };\n        const hideTooltip = () => {\n          tooltip.style.display = \"none\";\n        };\n        throughputContainer.addEventListener(\"mouseenter\", showTooltip);\n        throughputContainer.addEventListener(\"mouseleave\", hideTooltip);\n\n        throughputContainer.appendChild(throughputValueEl);\n      }\n\n      const headerSpacer = createElement(\"div\", \"persona-flex-1\");\n\n      // Filter dropdown\n      filterSelect = createElement(\n        \"select\",\n        \"persona-text-xs persona-bg-persona-surface persona-border persona-border-persona-border persona-rounded persona-px-2.5 persona-py-1 persona-text-persona-primary persona-cursor-pointer\"\n      ) as HTMLSelectElement;\n      const allOption = createElement(\"option\", \"\") as HTMLOptionElement;\n      allOption.value = \"\";\n      allOption.textContent = \"All events (0)\";\n      filterSelect.appendChild(allOption);\n\n      // Copy All button\n      const iconBtnClass =\n        \"persona-inline-flex persona-items-center persona-gap-1.5 persona-rounded persona-text-xs persona-text-persona-muted hover:persona-bg-persona-container hover:persona-text-persona-primary persona-cursor-pointer persona-border persona-border-persona-border persona-bg-persona-surface persona-flex-shrink-0 persona-px-2.5 persona-py-1\";\n\n      copyAllBtn = createElement(\n        \"button\",\n        iconBtnClass\n      ) as HTMLButtonElement;\n      copyAllBtn.type = \"button\";\n      copyAllBtn.title = \"Copy All\";\n      const copyAllIcon = renderLucideIcon(\n        \"clipboard-copy\",\n        \"12px\",\n        \"currentColor\",\n        1.5\n      );\n      if (copyAllIcon) copyAllBtn.appendChild(copyAllIcon);\n      const copyAllLabel = createElement(\n        \"span\",\n        \"persona-event-copy-all persona-text-xs\"\n      );\n      copyAllLabel.textContent = \"Copy All\";\n      copyAllBtn.appendChild(copyAllLabel);\n\n      if (throughputContainer) headerBar.appendChild(throughputContainer);\n      headerBar.appendChild(headerSpacer);\n      headerBar.appendChild(filterSelect);\n      headerBar.appendChild(copyAllBtn);\n\n      // --- Search bar ---\n      const searchBar = createElement(\n        \"div\",\n        \"persona-relative persona-px-4 persona-py-2.5 persona-border-b persona-border-persona-divider persona-bg-persona-surface\"\n      );\n      applyCustomClasses(searchBar, customClasses?.searchBar);\n\n      // Search icon\n      const searchIcon = renderLucideIcon(\n        \"search\",\n        \"14px\",\n        \"var(--persona-muted, #9ca3af)\",\n        1.5\n      );\n      const searchIconWrapper = createElement(\n        \"span\",\n        \"persona-absolute persona-left-6 persona-top-1/2 persona--translate-y-1/2 persona-pointer-events-none persona-flex persona-items-center\"\n      );\n      if (searchIcon) searchIconWrapper.appendChild(searchIcon);\n\n      searchInput = createElement(\n        \"input\",\n        \"persona-text-sm persona-bg-persona-surface persona-border persona-border-persona-border persona-rounded-md persona-pl-8 persona-pr-3 persona-py-1 persona-w-full persona-text-persona-primary\"\n      ) as HTMLInputElement;\n      applyCustomClasses(searchInput, customClasses?.searchInput);\n      searchInput.type = \"text\";\n      searchInput.placeholder = \"Search event payloads...\";\n\n      searchClearBtn = createElement(\n        \"button\",\n        \"persona-absolute persona-right-5 persona-top-1/2 persona--translate-y-1/2 persona-text-persona-muted hover:persona-text-persona-primary persona-cursor-pointer persona-border-none persona-bg-transparent persona-p-0 persona-leading-none\"\n      ) as HTMLButtonElement;\n      searchClearBtn.type = \"button\";\n      searchClearBtn.style.display = \"none\";\n      const clearSearchIcon = renderLucideIcon(\n        \"x\",\n        \"12px\",\n        \"currentColor\",\n        2\n      );\n      if (clearSearchIcon) searchClearBtn.appendChild(clearSearchIcon);\n\n      searchBar.appendChild(searchIconWrapper);\n      searchBar.appendChild(searchInput);\n      searchBar.appendChild(searchClearBtn);\n\n      toolbarOuter.appendChild(headerBar);\n      toolbarOuter.appendChild(searchBar);\n      if (throughputTooltipEl) toolbarOuter.appendChild(throughputTooltipEl);\n      return toolbarOuter;\n    }\n\n    // Build toolbar (with plugin hook)\n    let toolbar: HTMLElement;\n    const toolbarPlugin = plugins.find((p) => p.renderEventStreamToolbar);\n    if (toolbarPlugin?.renderEventStreamToolbar && config) {\n      const customToolbar = toolbarPlugin.renderEventStreamToolbar({\n        config,\n        defaultRenderer: () => buildDefaultToolbar(),\n        eventCount: buffer.getSize(),\n        filteredCount: 0,\n        onFilterChange: (type: string) => {\n          selectedType = type;\n          resetScrollState();\n          updateNow();\n        },\n        onSearchChange: (term: string) => {\n          searchTerm = term;\n          resetScrollState();\n          updateNow();\n        },\n      });\n      toolbar = customToolbar ?? buildDefaultToolbar();\n    } else {\n      toolbar = buildDefaultToolbar();\n    }\n\n    // ========================================================================\n    // Truncation Banner\n    // ========================================================================\n\n    const truncationBanner = createElement(\n      \"div\",\n      \"persona-text-xs persona-text-persona-muted persona-text-center persona-py-0.5 persona-px-4 persona-bg-persona-container persona-border-b persona-border-persona-divider persona-italic persona-flex-shrink-0\"\n    );\n    truncationBanner.style.display = \"none\";\n\n    // Refresh the inline header throughput value + hover tooltip. The elements\n    // live in the header bar (built by buildDefaultToolbar); this is a no-op\n    // when getThroughput is absent or a plugin replaced the toolbar.\n    function updateThroughputSummary(): void {\n      if (!getThroughput || !throughputValueEl || !throughputContainer) return;\n      const metric = getThroughput();\n      const value = formatThroughputValue(metric);\n      throughputValueEl.textContent = value;\n      // There's no visible \"Throughput\" label, so the accessible name carries it\n      // (plus the value). The detailed breakdown is revealed on hover via the\n      // custom tooltip and appended to the aria-label for assistive tech; when\n      // there's nothing to show, hide the tooltip so an empty box never flashes.\n      const meta = formatThroughputMeta(metric);\n      if (throughputTooltipEl) {\n        throughputTooltipEl.textContent = meta;\n        if (!meta) throughputTooltipEl.style.display = \"none\";\n      }\n      throughputContainer.setAttribute(\n        \"aria-label\",\n        meta ? `Throughput: ${value}, ${meta}` : `Throughput: ${value}`\n      );\n    }\n\n    // ========================================================================\n    // Events List (simple DOM, no virtual scroller)\n    // ========================================================================\n\n    const eventsListWrapper = createElement(\n      \"div\",\n      \"persona-flex-1 persona-min-h-0 persona-relative\"\n    );\n\n    const eventsList = createElement(\n      \"div\",\n      \"persona-event-stream-list persona-overflow-y-auto persona-min-h-0\"\n    );\n    eventsList.style.height = \"100%\";\n\n    // Scroll-to-bottom indicator\n    const scrollIndicator = createElement(\n      \"div\",\n      \"persona-scroll-to-bottom-indicator persona-absolute persona-bottom-3 persona-left-1/2 persona-transform persona--translate-x-1/2 persona-cursor-pointer persona-z-10 persona-text-xs\"\n    );\n    applyCustomClasses(scrollIndicator, customClasses?.scrollIndicator);\n    scrollIndicator.style.display = \"none\";\n    scrollIndicator.setAttribute(\n      \"data-persona-scroll-to-bottom-has-label\",\n      scrollToBottomLabel ? \"true\" : \"false\"\n    );\n    const arrowIcon = renderLucideIcon(\n      scrollToBottomIconName,\n      \"14px\",\n      \"currentColor\",\n      2\n    );\n    if (arrowIcon) scrollIndicator.appendChild(arrowIcon);\n    const indicatorText = createElement(\"span\", \"\");\n    indicatorText.textContent = scrollToBottomLabel;\n    scrollIndicator.appendChild(indicatorText);\n\n    // No matching events message\n    const noResultsMsg = createElement(\n      \"div\",\n      \"persona-flex persona-items-center persona-justify-center persona-h-full persona-text-sm persona-text-persona-muted\"\n    );\n    noResultsMsg.style.display = \"none\";\n\n    eventsListWrapper.appendChild(eventsList);\n    eventsListWrapper.appendChild(noResultsMsg);\n    eventsListWrapper.appendChild(scrollIndicator);\n\n    // ========================================================================\n    // Assemble container\n    // ========================================================================\n\n    container.setAttribute(\"tabindex\", \"0\");\n    container.appendChild(toolbar);\n    container.appendChild(truncationBanner);\n    container.appendChild(eventsListWrapper);\n\n    // ========================================================================\n    // Filtering & Search Logic\n    // ========================================================================\n\n    function updateFilterOptions() {\n      const allEvents = buffer.getAll();\n      const typeCounts: Record<string, number> = {};\n      for (const e of allEvents) {\n        typeCounts[e.type] = (typeCounts[e.type] || 0) + 1;\n      }\n      const types = Object.keys(typeCounts).sort();\n\n      const typesChanged =\n        types.length !== lastKnownTypes.length ||\n        !types.every((t, i) => t === lastKnownTypes[i]);\n      const countsChanged =\n        !typesChanged &&\n        types.some((t) => typeCounts[t] !== lastTypeCounts[t]);\n      const totalChanged =\n        allEvents.length !==\n        Object.values(lastTypeCounts).reduce((a, b) => a + b, 0);\n\n      if (!typesChanged && !countsChanged && !totalChanged) return;\n\n      lastKnownTypes = types;\n      lastTypeCounts = typeCounts;\n\n      if (!filterSelect) return;\n\n      const currentValue = filterSelect.value;\n\n      // Update \"All events\" option. The total is carried here (like each type\n      // option carries its own count) so a narrow panel can hide the standalone\n      // \"Events\" label + count badge without losing the total.\n      filterSelect.options[0].textContent = `All events (${allEvents.length})`;\n\n      if (typesChanged) {\n        while (filterSelect.options.length > 1) {\n          filterSelect.remove(1);\n        }\n        for (const type of types) {\n          const opt = createElement(\"option\", \"\") as HTMLOptionElement;\n          opt.value = type;\n          opt.textContent = `${type} (${typeCounts[type] || 0})`;\n          filterSelect.appendChild(opt);\n        }\n        if (currentValue && types.includes(currentValue)) {\n          filterSelect.value = currentValue;\n        } else if (currentValue) {\n          filterSelect.value = \"\";\n          selectedType = \"\";\n        }\n      } else {\n        for (let i = 1; i < filterSelect.options.length; i++) {\n          const opt = filterSelect.options[i];\n          opt.textContent = `${opt.value} (${typeCounts[opt.value] || 0})`;\n        }\n      }\n    }\n\n    function getFilteredEvents(): SSEEventRecord[] {\n      let events = buffer.getAll();\n      if (selectedType) {\n        events = events.filter((e) => e.type === selectedType);\n      }\n      if (searchTerm) {\n        const lower = searchTerm.toLowerCase();\n        events = events.filter(\n          (e) =>\n            e.type.toLowerCase().includes(lower) ||\n            e.payload.toLowerCase().includes(lower)\n        );\n      }\n      return events;\n    }\n\n    function hasActiveFilters(): boolean {\n      return selectedType !== \"\" || searchTerm !== \"\";\n    }\n\n    function resetScrollState() {\n      lastFilteredCount = 0;\n      newEventsSincePause = 0;\n      autoFollow.resume();\n      scrollIndicator.style.display = \"none\";\n    }\n\n    function toggleExpand(eventId: string) {\n      if (expandedSet.has(eventId)) {\n        expandedSet.delete(eventId);\n      } else {\n        expandedSet.add(eventId);\n      }\n      dirtyExpandId = eventId;\n      // Save scroll position: user-initiated expand/collapse should not auto-scroll\n      const savedScrollTop = eventsList.scrollTop;\n      const wasAutoFollowing = autoFollow.isFollowing();\n      suppressScrollHandler = true;\n      autoFollow.pause(); // prevent auto-scroll during re-render\n      updateNow();\n      eventsList.scrollTop = savedScrollTop;\n      if (wasAutoFollowing) {\n        autoFollow.resume();\n      }\n      suppressScrollHandler = false;\n    }\n\n    // ========================================================================\n    // Render Logic\n    // ========================================================================\n\n    function isNearBottom(): boolean {\n      return isElementNearBottom(eventsList, 50);\n    }\n\n    function updateNow() {\n      lastRenderTime = Date.now();\n      pendingUpdate = false;\n\n      updateThroughputSummary();\n      updateFilterOptions();\n\n      // Truncation banner\n      const evictedCount = buffer.getEvictedCount();\n      if (evictedCount > 0) {\n        truncationBanner.textContent = `${evictedCount.toLocaleString()} older events truncated`;\n        truncationBanner.style.display = \"\";\n      } else {\n        truncationBanner.style.display = \"none\";\n      }\n\n      filteredEvents = getFilteredEvents();\n      const newCount = filteredEvents.length;\n      const bufferHasEvents = buffer.getSize() > 0;\n\n      // Show/hide no-results message\n      if (newCount === 0 && bufferHasEvents && hasActiveFilters()) {\n        noResultsMsg.textContent = searchTerm\n          ? `No events matching '${searchTerm}'`\n          : \"No events matching filter\";\n        noResultsMsg.style.display = \"\";\n        eventsList.style.display = \"none\";\n      } else {\n        noResultsMsg.style.display = \"none\";\n        eventsList.style.display = \"\";\n      }\n\n      // Update Copy All button title\n      if (copyAllBtn) {\n        copyAllBtn.title = hasActiveFilters()\n          ? `Copy Filtered (${newCount})`\n          : \"Copy All\";\n      }\n\n      // Track new events since user scrolled up\n      if (scrollToBottomEnabled && !autoFollow.isFollowing() && newCount > lastFilteredCount) {\n        newEventsSincePause += newCount - lastFilteredCount;\n        indicatorText.textContent = scrollToBottomLabel\n          ? `${scrollToBottomLabel}${newEventsSincePause > 0 ? ` (${newEventsSincePause})` : \"\"}`\n          : \"\";\n        scrollIndicator.style.display = \"\";\n      }\n      lastFilteredCount = newCount;\n\n      // Get first event timestamp for relative time calculations\n      // Use the unfiltered first event for consistent time references\n      const allEvents = buffer.getAll();\n      const firstTimestamp =\n        allEvents.length > 0 ? allEvents[0].timestamp : 0;\n\n      // Clean up expanded state for evicted events\n      const currentIds = new Set(filteredEvents.map((e) => e.id));\n      for (const id of expandedSet) {\n        if (!currentIds.has(id)) expandedSet.delete(id);\n      }\n\n      // Determine which rendering path to use\n      const filterChanged =\n        selectedType !== lastRenderedFilter ||\n        searchTerm !== lastRenderedSearch;\n      const isFirstRender = rowElements.size === 0 && filteredEvents.length > 0;\n\n      if (filterChanged || isFirstRender || filteredEvents.length === 0) {\n        // Path A: Full rebuild (filter/search changed, first render, or empty list)\n        eventsList.innerHTML = \"\";\n        rowElements.clear();\n        const fragment = document.createDocumentFragment();\n        for (let i = 0; i < filteredEvents.length; i++) {\n          const row = renderEventRow(\n            filteredEvents[i],\n            i,\n            firstTimestamp,\n            esConfig,\n            expandedSet,\n            toggleExpand,\n            plugins,\n            config\n          );\n          rowElements.set(filteredEvents[i].id, row);\n          fragment.appendChild(row);\n        }\n        eventsList.appendChild(fragment);\n        lastRenderedFilter = selectedType;\n        lastRenderedSearch = searchTerm;\n        dirtyExpandId = null;\n      } else {\n        // Path B: Single row replace (expand/collapse)\n        if (dirtyExpandId !== null) {\n          const oldRow = rowElements.get(dirtyExpandId);\n          if (oldRow && oldRow.parentNode === eventsList) {\n            // Find the index of this event in filteredEvents for correct seq number\n            const idx = filteredEvents.findIndex(\n              (e) => e.id === dirtyExpandId\n            );\n            if (idx >= 0) {\n              const newRow = renderEventRow(\n                filteredEvents[idx],\n                idx,\n                firstTimestamp,\n                esConfig,\n                expandedSet,\n                toggleExpand,\n                plugins,\n                config\n              );\n              eventsList.insertBefore(newRow, oldRow);\n              oldRow.remove();\n              rowElements.set(dirtyExpandId, newRow);\n            }\n          }\n          dirtyExpandId = null;\n        }\n\n        // Path C: Incremental append (default streaming path)\n        // Remove evicted rows\n        const activeIds = new Set(filteredEvents.map((e) => e.id));\n        for (const [id, el] of rowElements) {\n          if (!activeIds.has(id)) {\n            el.remove();\n            rowElements.delete(id);\n          }\n        }\n        // Append new rows (events not yet in rowElements)\n        for (let i = 0; i < filteredEvents.length; i++) {\n          const evt = filteredEvents[i];\n          if (!rowElements.has(evt.id)) {\n            const row = renderEventRow(\n              evt,\n              i,\n              firstTimestamp,\n              esConfig,\n              expandedSet,\n              toggleExpand,\n              plugins,\n              config\n            );\n            rowElements.set(evt.id, row);\n            eventsList.appendChild(row);\n          }\n        }\n      }\n\n      // Auto-scroll if user hasn't scrolled up\n      if (autoFollow.isFollowing()) {\n        eventsList.scrollTop = eventsList.scrollHeight;\n      }\n    }\n\n    function update() {\n      const now = Date.now();\n      const elapsed = now - lastRenderTime;\n\n      if (elapsed >= UPDATE_THROTTLE_MS) {\n        if (pendingRafId !== null) {\n          cancelAnimationFrame(pendingRafId);\n          pendingRafId = null;\n        }\n        updateNow();\n        return;\n      }\n\n      if (!pendingUpdate) {\n        pendingUpdate = true;\n        pendingRafId = requestAnimationFrame(() => {\n          pendingRafId = null;\n          updateNow();\n        });\n      }\n    }\n\n    // ========================================================================\n    // Event Handlers\n    // ========================================================================\n\n    const swapCopyAllIcon = (\n      iconName: string,\n      restoreAfterMs: number\n    ) => {\n      if (!copyAllBtn) return;\n      copyAllBtn.innerHTML = \"\";\n      const icon = renderLucideIcon(\n        iconName,\n        \"12px\",\n        \"currentColor\",\n        1.5\n      );\n      if (icon) copyAllBtn.appendChild(icon);\n      const label = createElement(\"span\", \"persona-text-xs\");\n      label.textContent = \"Copy All\";\n      copyAllBtn.appendChild(label);\n      setTimeout(() => {\n        copyAllBtn.innerHTML = \"\";\n        const original = renderLucideIcon(\n          \"clipboard-copy\",\n          \"12px\",\n          \"currentColor\",\n          1.5\n        );\n        if (original) copyAllBtn.appendChild(original);\n        const restoreLabel = createElement(\"span\", \"persona-text-xs\");\n        restoreLabel.textContent = \"Copy All\";\n        copyAllBtn.appendChild(restoreLabel);\n        copyAllBtn.disabled = false;\n      }, restoreAfterMs);\n    };\n\n    const handleCopyAll = async () => {\n      if (!copyAllBtn) return;\n      copyAllBtn.disabled = true;\n      try {\n        let events: SSEEventRecord[];\n        if (hasActiveFilters()) {\n          events = filteredEvents;\n        } else {\n          if (getFullHistory) {\n            events = await getFullHistory();\n            if (events.length === 0) events = buffer.getAll();\n          } else {\n            events = buffer.getAll();\n          }\n        }\n        const parsed = events.map((e) => {\n          try {\n            return JSON.parse(e.payload);\n          } catch {\n            return e.payload;\n          }\n        });\n        await navigator.clipboard.writeText(\n          JSON.stringify(parsed, null, 2)\n        );\n        swapCopyAllIcon(\"check\", 1500);\n      } catch {\n        swapCopyAllIcon(\"x\", 1500);\n      }\n    };\n\n    const handleFilterChange = () => {\n      if (!filterSelect) return;\n      selectedType = filterSelect.value;\n      resetScrollState();\n      updateNow();\n    };\n\n    const handleSearchInput = () => {\n      if (!searchInput || !searchClearBtn) return;\n      searchClearBtn.style.display = searchInput.value ? \"\" : \"none\";\n      if (searchTimeout) clearTimeout(searchTimeout);\n      searchTimeout = setTimeout(() => {\n        searchTerm = searchInput.value;\n        resetScrollState();\n        updateNow();\n      }, 150);\n    };\n\n    const handleSearchClear = () => {\n      if (!searchInput || !searchClearBtn) return;\n      searchInput.value = \"\";\n      searchTerm = \"\";\n      searchClearBtn.style.display = \"none\";\n      if (searchTimeout) clearTimeout(searchTimeout);\n      resetScrollState();\n      updateNow();\n    };\n\n    const handleListScroll = () => {\n      if (suppressScrollHandler) return;\n      const currentScrollTop = eventsList.scrollTop;\n      const { action, nextLastScrollTop } = resolveFollowStateFromScroll({\n        following: autoFollow.isFollowing(),\n        currentScrollTop,\n        lastScrollTop,\n        nearBottom: isNearBottom(),\n        userScrollThreshold: 1,\n        resumeRequiresDownwardScroll: true\n      });\n      lastScrollTop = nextLastScrollTop;\n\n      if (action === \"resume\") {\n        autoFollow.resume();\n        newEventsSincePause = 0;\n        scrollIndicator.style.display = \"none\";\n      } else if (action === \"pause\") {\n        autoFollow.pause();\n        if (scrollToBottomEnabled) {\n          indicatorText.textContent = scrollToBottomLabel;\n          scrollIndicator.style.display = \"\";\n        }\n      }\n    };\n\n    // Wheel events fire synchronously before rAF callbacks, so we can\n    // detect upward scroll intent before the next updateNow() auto-scrolls.\n    const handleWheel = (e: WheelEvent) => {\n      const action = resolveFollowStateFromWheel({\n        following: autoFollow.isFollowing(),\n        deltaY: e.deltaY,\n        nearBottom: isNearBottom(),\n        resumeWhenNearBottom: true\n      });\n\n      if (action === \"pause\") {\n        autoFollow.pause();\n        if (scrollToBottomEnabled) {\n          indicatorText.textContent = scrollToBottomLabel;\n          scrollIndicator.style.display = \"\";\n        }\n      } else if (action === \"resume\") {\n        autoFollow.resume();\n        newEventsSincePause = 0;\n        scrollIndicator.style.display = \"none\";\n      }\n    };\n\n    const handleScrollIndicatorClick = () => {\n      if (!scrollToBottomEnabled) return;\n      eventsList.scrollTop = eventsList.scrollHeight;\n      autoFollow.resume();\n      newEventsSincePause = 0;\n      scrollIndicator.style.display = \"none\";\n    };\n\n    // Delegated click handler for expand/collapse (survives DOM rebuilds)\n    const handleRowClick = (e: Event) => {\n      const target = e.target as Element;\n      if (!target) return;\n      // Skip if clicking copy button or its children\n      if (target.closest(\"button\")) return;\n      // Find the closest row with an event ID\n      const row = target.closest(\"[data-event-id]\") as HTMLElement | null;\n      if (!row) return;\n      const eventId = row.getAttribute(\"data-event-id\");\n      if (eventId) toggleExpand(eventId);\n    };\n\n    // Keyboard shortcuts\n    const handleKeyDown = (e: KeyboardEvent) => {\n      const isMod = e.metaKey || e.ctrlKey;\n\n      if (isMod && e.key === \"f\") {\n        e.preventDefault();\n        searchInput?.focus();\n        searchInput?.select();\n        return;\n      }\n\n      if (e.key === \"Escape\") {\n        if (\n          searchInput &&\n          document.activeElement === searchInput\n        ) {\n          handleSearchClear();\n          searchInput.blur();\n          container.focus();\n        } else if (onClose) {\n          onClose();\n        }\n      }\n    };\n\n    // ========================================================================\n    // Wire up event listeners\n    // ========================================================================\n\n    if (copyAllBtn) copyAllBtn.addEventListener(\"click\", handleCopyAll);\n    if (filterSelect)\n      filterSelect.addEventListener(\"change\", handleFilterChange);\n    if (searchInput)\n      searchInput.addEventListener(\"input\", handleSearchInput);\n    if (searchClearBtn)\n      searchClearBtn.addEventListener(\"click\", handleSearchClear);\n    eventsList.addEventListener(\"scroll\", handleListScroll);\n    eventsList.addEventListener(\"wheel\", handleWheel, { passive: true });\n    eventsList.addEventListener(\"click\", handleRowClick);\n    scrollIndicator.addEventListener(\"click\", handleScrollIndicatorClick);\n    container.addEventListener(\"keydown\", handleKeyDown);\n\n    // ========================================================================\n    // Destroy / Cleanup\n    // ========================================================================\n\n    function destroy() {\n      if (searchTimeout) clearTimeout(searchTimeout);\n      if (pendingRafId !== null) {\n        cancelAnimationFrame(pendingRafId);\n        pendingRafId = null;\n      }\n      pendingUpdate = false;\n      rowElements.clear();\n      if (copyAllBtn)\n        copyAllBtn.removeEventListener(\"click\", handleCopyAll);\n      if (filterSelect)\n        filterSelect.removeEventListener(\"change\", handleFilterChange);\n      if (searchInput)\n        searchInput.removeEventListener(\"input\", handleSearchInput);\n      if (searchClearBtn)\n        searchClearBtn.removeEventListener(\"click\", handleSearchClear);\n      eventsList.removeEventListener(\"scroll\", handleListScroll);\n      eventsList.removeEventListener(\"wheel\", handleWheel);\n      eventsList.removeEventListener(\"click\", handleRowClick);\n      scrollIndicator.removeEventListener(\n        \"click\",\n        handleScrollIndicatorClick\n      );\n      container.removeEventListener(\"keydown\", handleKeyDown);\n    }\n\n    return { element: container, update, destroy };\n  }\n}\n","/**\n * Roving-tabindex tablist controller (WAI-ARIA APG tabs pattern). Owns the tab\n * roles, aria-selected, roving tabindex, Arrow/Home/End keyboard nav routed\n * through an onSelect callback, focus reveal via scrollIntoView, and focus\n * survival across a tab-DOM rebuild. Extracted so custom tab bars stay\n * accessible without re-implementing the pattern.\n */\n\nexport interface RovingTablistOptions {\n  onSelect: (index: number) => void;\n  /** Arrow-key axis. @default \"horizontal\" */\n  orientation?: \"horizontal\" | \"vertical\";\n}\n\nexport interface RovingTablistController {\n  /** Snapshot whether focus is inside the strip. Call before replacing tab DOM. */\n  beforeRender(): void;\n  /**\n   * Apply role=tablist/tab, aria-selected, roving tabindex, and keyboard nav to\n   * the given tab elements (in order). Restores focus to the selected tab when\n   * the last beforeRender() saw focus inside the strip. Call after (re)building\n   * tabs.\n   */\n  render(tabs: HTMLElement[], selectedIndex: number): void;\n  destroy(): void;\n}\n\nexport function createRovingTablist(\n  container: HTMLElement,\n  options: RovingTablistOptions\n): RovingTablistController {\n  const orientation = options.orientation ?? \"horizontal\";\n  const prevKey = orientation === \"vertical\" ? \"ArrowUp\" : \"ArrowLeft\";\n  const nextKey = orientation === \"vertical\" ? \"ArrowDown\" : \"ArrowRight\";\n  container.setAttribute(\"role\", \"tablist\");\n\n  let tabs: HTMLElement[] = [];\n  // Whether the last beforeRender() saw focus inside the strip; consumed by the\n  // next render() to restore the roving stop after a rebuild.\n  let restoreFocus = false;\n\n  const revealTab = (tab: HTMLElement) => {\n    if (typeof tab.scrollIntoView === \"function\") {\n      tab.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });\n    }\n  };\n\n  const indexOfTarget = (target: EventTarget | null): number => {\n    const node = target as HTMLElement | null;\n    return tabs.findIndex((t) => t === node || t.contains(node));\n  };\n\n  // One delegated keydown listener so it survives child replacement. Arrow moves\n  // selection one step (clamped); Home/End jump to the edges; selection routes\n  // through onSelect so the host owns state.\n  const onKeydown = (e: KeyboardEvent) => {\n    const index = indexOfTarget(e.target);\n    if (index < 0) return;\n    let next = index;\n    if (e.key === nextKey) next = Math.min(index + 1, tabs.length - 1);\n    else if (e.key === prevKey) next = Math.max(index - 1, 0);\n    else if (e.key === \"Home\") next = 0;\n    else if (e.key === \"End\") next = tabs.length - 1;\n    else return;\n    e.preventDefault();\n    if (next === index) return;\n    options.onSelect(next);\n  };\n\n  // Delegated focus reveal: bring a focused edge tab out from under the fade.\n  const onFocusin = (e: FocusEvent) => {\n    const index = indexOfTarget(e.target);\n    if (index >= 0) revealTab(tabs[index]);\n  };\n\n  container.addEventListener(\"keydown\", onKeydown);\n  container.addEventListener(\"focusin\", onFocusin);\n\n  return {\n    beforeRender() {\n      restoreFocus =\n        typeof document !== \"undefined\" &&\n        container.contains(document.activeElement);\n    },\n    render(nextTabs: HTMLElement[], selectedIndex: number) {\n      tabs = nextTabs;\n      nextTabs.forEach((tab, i) => {\n        tab.setAttribute(\"role\", \"tab\");\n        const selected = i === selectedIndex;\n        tab.setAttribute(\"aria-selected\", selected ? \"true\" : \"false\");\n        // Roving tabindex: the selected tab is the only stop, or the first tab\n        // when nothing is selected.\n        tab.tabIndex = selected || (selectedIndex < 0 && i === 0) ? 0 : -1;\n      });\n      // Restore focus to the roving stop after the rebuild so arrow nav keeps\n      // working across the selection re-render.\n      if (restoreFocus) {\n        const stop =\n          (selectedIndex >= 0 ? nextTabs[selectedIndex] : undefined) ?? nextTabs[0];\n        if (stop && typeof stop.focus === \"function\") {\n          revealTab(stop);\n          stop.focus();\n        }\n      }\n    },\n    destroy() {\n      container.removeEventListener(\"keydown\", onKeydown);\n      container.removeEventListener(\"focusin\", onFocusin);\n    },\n  };\n}\n","import { createElement } from \"../utils/dom\";\nimport type {\n  AgentWidgetConfig,\n  PersonaArtifactRecord,\n  PersonaArtifactCustomAction,\n} from \"../types\";\nimport { fileTypeLabel, basenameOf } from \"../utils/artifact-file\";\nimport {\n  buildArtifactActionButton,\n  artifactRecordActionContext,\n} from \"../utils/artifact-custom-actions\";\nimport {\n  renderArtifactPreviewBody,\n  artifactCopyText,\n  type ArtifactPreviewBodyHandle,\n} from \"./artifact-preview\";\nimport { renderLucideIcon } from \"../utils/icons\";\nimport { createDropdownMenu, type DropdownMenuHandle } from \"../utils/dropdown\";\nimport { createIconButton, createLabelButton, createToggleGroup } from \"../utils/buttons\";\nimport { createRovingTablist } from \"../utils/roving-tablist\";\n\nexport type ArtifactPaneApi = {\n  element: HTMLElement;\n  /** Backdrop for mobile drawer (optional separate layer) */\n  backdrop: HTMLElement | null;\n  update: (state: { artifacts: PersonaArtifactRecord[]; selectedId: string | null }) => void;\n  setMobileOpen: (open: boolean) => void;\n  /** Reflect the expanded state on the toolbar toggle (icon + label). */\n  setExpanded: (expanded: boolean) => void;\n  /** Show/hide the expand toggle; driven from the parent's config on every sync so live config updates apply. */\n  setExpandToggleVisible: (visible: boolean) => void;\n  /** Show/hide the default-preset copy button; no-op in the document preset (copy is always shown there). */\n  setCopyButtonVisible: (visible: boolean) => void;\n  /** Replace the toolbar custom-action list and re-render it; driven from the parent's config on every sync so live config updates apply. */\n  setCustomActions: (actions: PersonaArtifactCustomAction[]) => void;\n  /** Update the tab-strip edge fade live; driven from the parent's config on every sync. */\n  setTabFade: (cfg: boolean | { start?: boolean; end?: boolean } | undefined) => void;\n  /** Swap the tab bar live between the built-in strip (undefined) and a host renderTabBar; driven from the parent's config on every sync. */\n  setRenderTabBar: (\n    fn:\n      | ((ctx: {\n          records: PersonaArtifactRecord[];\n          selectedId: string | null;\n          onSelect: (id: string) => void;\n        }) => HTMLElement)\n      | undefined\n  ) => void;\n  /**\n   * Explicit \"the pane surface is shown to the user\" signal from the host\n   * (ui.ts), which owns the display-mode / expand / mobile-drawer routing.\n   * While hidden the pane records state on update() but does NOT build/refresh\n   * the preview DOM, so an inline/card artifact never spawns a second\n   * sandboxed iframe (which would execute artifact scripts a second time). On\n   * reveal the pane renders the current recorded state.\n   */\n  setVisible: (visible: boolean) => void;\n};\n\n/**\n * Right-hand artifact sidebar / mobile drawer content.\n */\nexport function createArtifactPane(\n  config: AgentWidgetConfig,\n  options: {\n    onSelect: (id: string) => void;\n    /** User closed the pane (mobile drawer or split sidebar): parent should persist “hidden until reopened”. */\n    onDismiss?: () => void;\n    /** User clicked the expand/collapse toggle: parent owns the state and calls back via setExpanded. */\n    onToggleExpand?: () => void;\n  }\n): ArtifactPaneApi {\n  const layout = config.features?.artifacts?.layout;\n  const toolbarPreset = layout?.toolbarPreset ?? \"default\";\n  const documentChrome = toolbarPreset === \"document\";\n  const toolbarTitle = layout?.toolbarTitle ?? \"Artifacts\";\n  const closeButtonLabel = layout?.closeButtonLabel ?? \"Close\";\n  const panePadding = layout?.panePadding?.trim();\n\n  const tabFadeSize = layout?.tabFadeSize?.trim();\n  // Per-edge fade enablement is mutable so setTabFade can switch it live (the\n  // host re-reads config on every sync). false disables both edges; the object\n  // form gates each edge; true/undefined keeps both dynamic.\n  const resolveFade = (\n    cfg: boolean | { start?: boolean; end?: boolean } | undefined\n  ) => ({\n    start: cfg === false ? false : typeof cfg === \"object\" && cfg ? cfg.start !== false : true,\n    end: cfg === false ? false : typeof cfg === \"object\" && cfg ? cfg.end !== false : true,\n  });\n  let { start: fadeStartEnabled, end: fadeEndEnabled } = resolveFade(layout?.tabFade);\n\n  const backdrop =\n    typeof document !== \"undefined\"\n      ? createElement(\n          \"div\",\n          \"persona-artifact-backdrop persona-fixed persona-inset-0 persona-z-[55] persona-bg-black/30 persona-hidden md:persona-hidden\"\n        )\n      : null;\n  const dismissLocalUi = () => {\n    backdrop?.classList.add(\"persona-hidden\");\n    shell.classList.remove(\"persona-artifact-drawer-open\");\n    // Hide portaled copy menu\n    copyMenuDropdown?.hide();\n  };\n\n  if (backdrop) {\n    backdrop.addEventListener(\"click\", () => {\n      dismissLocalUi();\n      options.onDismiss?.();\n    });\n  }\n\n  const shell = createElement(\n    \"aside\",\n    \"persona-artifact-pane persona-flex persona-flex-col persona-min-h-0 persona-min-w-0 persona-bg-persona-surface persona-text-persona-primary persona-border-l persona-border-persona-border\"\n  );\n  shell.setAttribute(\"data-persona-theme-zone\", \"artifact-pane\");\n  if (documentChrome) {\n    shell.classList.add(\"persona-artifact-pane-document\");\n  }\n\n  const toolbar = createElement(\n    \"div\",\n    \"persona-artifact-toolbar persona-flex persona-items-center persona-justify-between persona-gap-2 persona-px-2 persona-py-2 persona-border-b persona-border-persona-border persona-shrink-0\"\n  );\n  toolbar.setAttribute(\"data-persona-theme-zone\", \"artifact-toolbar\");\n  if (documentChrome) {\n    toolbar.classList.add(\"persona-artifact-toolbar-document\");\n  }\n  const titleEl = createElement(\"span\", \"persona-text-xs persona-font-medium persona-truncate\");\n  titleEl.textContent = toolbarTitle;\n\n  const closeBtn = createIconButton({ icon: \"x\", label: closeButtonLabel });\n  closeBtn.addEventListener(\"click\", () => {\n    dismissLocalUi();\n    options.onDismiss?.();\n  });\n\n  /** Document preset: view vs raw source */\n  let viewMode: \"rendered\" | \"source\" = \"rendered\";\n  const viewToggle = createToggleGroup({\n    items: [\n      {\n        id: \"rendered\",\n        icon: \"eye\",\n        label: \"Rendered view\",\n        className: documentChrome ? \"persona-artifact-doc-icon-btn persona-artifact-view-btn\" : undefined,\n      },\n      {\n        id: \"source\",\n        icon: \"code-xml\",\n        label: \"Source\",\n        className: documentChrome ? \"persona-artifact-doc-icon-btn persona-artifact-code-btn\" : undefined,\n      },\n    ],\n    selectedId: \"rendered\",\n    className: \"persona-artifact-toggle-group persona-shrink-0\",\n    onSelect: (id) => {\n      viewMode = id === \"source\" ? \"source\" : \"rendered\";\n      render();\n    },\n  });\n  const actionsRight = createElement(\"div\", \"persona-flex persona-items-center persona-gap-1 persona-shrink-0\");\n  const showCopyLabel = layout?.documentToolbarShowCopyLabel === true;\n  const showCopyChevron = layout?.documentToolbarShowCopyChevron === true;\n  const copyMenuItems = layout?.documentToolbarCopyMenuItems;\n  const showCopyMenu = Boolean(showCopyChevron && copyMenuItems && copyMenuItems.length > 0);\n\n  let copyWrap: HTMLElement | null = null;\n  let copyBtn: HTMLButtonElement;\n  let copyMenuChevronBtn: HTMLButtonElement | null = null;\n  let copyMenuDropdown: DropdownMenuHandle | null = null;\n\n  if (documentChrome && (showCopyLabel || showCopyChevron) && !showCopyMenu) {\n    copyBtn = showCopyLabel\n      ? createLabelButton({ icon: \"copy\", label: \"Copy\", iconSize: 14, className: \"persona-artifact-doc-copy-btn\" })\n      : createIconButton({ icon: \"copy\", label: \"Copy\", className: \"persona-artifact-doc-copy-btn\" });\n    if (showCopyChevron) {\n      const chev = renderLucideIcon(\"chevron-down\", 14, \"currentColor\", 2);\n      if (chev) copyBtn.appendChild(chev);\n    }\n  } else if (documentChrome && showCopyMenu) {\n    copyWrap = createElement(\n      \"div\",\n      \"persona-relative persona-inline-flex persona-items-center persona-gap-0 persona-rounded-md\"\n    );\n    copyBtn = showCopyLabel\n      ? createLabelButton({ icon: \"copy\", label: \"Copy\", iconSize: 14, className: \"persona-artifact-doc-copy-btn\" })\n      : createIconButton({ icon: \"copy\", label: \"Copy\", className: \"persona-artifact-doc-copy-btn\" });\n    copyMenuChevronBtn = createIconButton({\n      icon: \"chevron-down\",\n      label: \"More copy options\",\n      size: 14,\n      className: \"persona-artifact-doc-copy-menu-chevron persona-artifact-doc-icon-btn\",\n      aria: { \"aria-haspopup\": \"true\", \"aria-expanded\": \"false\" }\n    });\n    copyWrap.append(copyBtn, copyMenuChevronBtn);\n  } else if (documentChrome) {\n    copyBtn = createIconButton({ icon: \"copy\", label: \"Copy\", className: \"persona-artifact-doc-icon-btn\" });\n  } else {\n    // Always built (like the expand toggle) so a live config update can\n    // reveal it via setCopyButtonVisible; hidden unless layout opts in.\n    copyBtn = createIconButton({ icon: \"copy\", label: \"Copy\" });\n    if (layout?.showCopyButton !== true) {\n      copyBtn.classList.add(\"persona-hidden\");\n    }\n  }\n\n  const refreshBtn = documentChrome\n    ? createIconButton({ icon: \"refresh-cw\", label: \"Refresh\", className: \"persona-artifact-doc-icon-btn\" })\n    : createIconButton({ icon: \"refresh-cw\", label: \"Refresh\" });\n  const closeIconBtn = documentChrome\n    ? createIconButton({ icon: \"x\", label: closeButtonLabel, className: \"persona-artifact-doc-icon-btn\" })\n    : createIconButton({ icon: \"x\", label: closeButtonLabel });\n\n  // Always built so a live config update can reveal it (the pane is created\n  // once; syncArtifactPane re-reads the config and drives setExpandToggleVisible).\n  const expandBtn = createIconButton({\n    icon: \"maximize\",\n    label: \"Expand artifacts panel\",\n    className: \"persona-artifact-expand-btn\" + (documentChrome ? \" persona-artifact-doc-icon-btn\" : \"\"),\n    onClick: () => options.onToggleExpand?.(),\n  });\n  if (layout?.showExpandToggle !== true) {\n    expandBtn.classList.add(\"persona-hidden\");\n  }\n\n  // Host container for integrator-supplied toolbar buttons. Built once and\n  // slotted into whichever preset renders; setCustomActions re-renders its\n  // children on every parent sync so live config updates apply.\n  const customActions = createElement(\n    \"div\",\n    \"persona-flex persona-items-center persona-gap-1 persona-shrink-0 persona-artifact-toolbar-custom-actions\"\n  );\n  let customActionList: PersonaArtifactCustomAction[] =\n    config.features?.artifacts?.toolbarActions ?? [];\n\n  const getSelectedArtifactText = (): { markdown: string; jsonPayload: string; id: string | null } => {\n    const sel = records.find((r) => r.id === selectedId) ?? records[records.length - 1];\n    const id = sel?.id ?? null;\n    const markdown = sel?.artifactType === \"markdown\" ? sel.markdown ?? \"\" : \"\";\n    const jsonPayload = sel\n      ? JSON.stringify({ component: sel.component, props: sel.props }, null, 2)\n      : \"\";\n    return { markdown, jsonPayload, id };\n  };\n\n  const defaultCopy = async () => {\n    const sel = records.find((r) => r.id === selectedId) ?? records[records.length - 1];\n    try {\n      await navigator.clipboard.writeText(artifactCopyText(sel));\n    } catch {\n      /* ignore */\n    }\n  };\n\n  copyBtn.addEventListener(\"click\", async () => {\n    const handler = layout?.onDocumentToolbarCopyMenuSelect;\n    if (handler && showCopyMenu) {\n      const { markdown, jsonPayload, id } = getSelectedArtifactText();\n      try {\n        await handler({ actionId: \"primary\", artifactId: id, markdown, jsonPayload });\n      } catch {\n        /* ignore */\n      }\n      return;\n    }\n    await defaultCopy();\n  });\n\n  if (copyMenuChevronBtn && copyMenuItems?.length) {\n    // Resolve the portal target: widget root for CSS var inheritance, escaping overflow: hidden\n    const resolvePortal = (): HTMLElement => shell.closest(\"[data-persona-root]\") as HTMLElement ?? document.body;\n\n    const initDropdown = () => {\n      copyMenuDropdown = createDropdownMenu({\n        items: copyMenuItems.map((item) => ({ id: item.id, label: item.label })),\n        onSelect: async (actionId) => {\n          const { markdown, jsonPayload, id } = getSelectedArtifactText();\n          const handler = layout?.onDocumentToolbarCopyMenuSelect;\n          try {\n            if (handler) {\n              await handler({ actionId, artifactId: id, markdown, jsonPayload });\n            } else if (actionId === \"markdown\" || actionId === \"md\") {\n              await navigator.clipboard.writeText(markdown);\n            } else if (actionId === \"json\" || actionId === \"source\") {\n              await navigator.clipboard.writeText(jsonPayload);\n            } else {\n              await navigator.clipboard.writeText(markdown || jsonPayload);\n            }\n          } catch {\n            /* ignore */\n          }\n        },\n        anchor: copyWrap ?? copyMenuChevronBtn!,\n        position: 'bottom-right',\n        portal: resolvePortal(),\n      });\n    };\n\n    // Defer init until shell is in the DOM (may not be attached yet)\n    if (shell.isConnected) {\n      initDropdown();\n    } else {\n      requestAnimationFrame(initDropdown);\n    }\n\n    copyMenuChevronBtn.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      copyMenuDropdown?.toggle();\n    });\n  }\n\n  refreshBtn.addEventListener(\"click\", async () => {\n    try {\n      await layout?.onDocumentToolbarRefresh?.();\n    } catch {\n      /* ignore */\n    }\n    render();\n  });\n  closeIconBtn.addEventListener(\"click\", () => {\n    dismissLocalUi();\n    options.onDismiss?.();\n  });\n\n  const centerTitle = createElement(\n    \"span\",\n    \"persona-min-w-0 persona-flex-1 persona-text-xs persona-font-medium persona-text-persona-primary persona-truncate persona-text-center md:persona-text-left\"\n  );\n\n  if (documentChrome) {\n    toolbar.replaceChildren();\n    if (copyWrap) {\n      actionsRight.append(copyWrap, refreshBtn, closeIconBtn);\n    } else {\n      actionsRight.append(copyBtn, refreshBtn, closeIconBtn);\n    }\n    // Order: copy, refresh, custom, expand, close.\n    actionsRight.insertBefore(customActions, closeIconBtn);\n    actionsRight.insertBefore(expandBtn, closeIconBtn);\n    toolbar.append(viewToggle.element, centerTitle, actionsRight);\n  } else {\n    // Group expand + Close so the toolbar's justify-between spaces\n    // [toggle] [title] [actions] instead of distributing each button.\n    const defaultActions = createElement(\n      \"div\",\n      \"persona-flex persona-items-center persona-gap-1 persona-shrink-0\"\n    );\n    // Order: title, copy, custom, expand, Close.\n    defaultActions.append(copyBtn, customActions, expandBtn, closeBtn);\n    toolbar.appendChild(titleEl);\n    toolbar.appendChild(defaultActions);\n  }\n\n  if (panePadding) {\n    toolbar.style.paddingLeft = panePadding;\n    toolbar.style.paddingRight = panePadding;\n  }\n\n  const list = createElement(\n    \"div\",\n    \"persona-artifact-list persona-shrink-0 persona-flex persona-gap-1 persona-overflow-x-auto persona-p-2 persona-border-b persona-border-persona-border\"\n  );\n  if (tabFadeSize) {\n    list.style.setProperty(\"--persona-artifact-tab-fade-size\", tabFadeSize);\n  }\n\n  // Seam A: a full tab-bar replacement. Both the built-in strip and a custom\n  // mount are always built so renderTabBar can switch live (the host re-reads\n  // config on every sync); render() shows the active one and hides the other.\n  let currentRenderTabBar = config.features?.artifacts?.renderTabBar;\n  // Stable host for the replacement bar, mounted where the strip goes; hidden\n  // until a custom bar is active.\n  const customBarMount = createElement(\n    \"div\",\n    \"persona-artifact-tab-custom persona-shrink-0 persona-border-b persona-border-persona-border persona-hidden\"\n  );\n\n  // Accessible tablist behavior for the built-in strip (roles, roving tabindex,\n  // Arrow/Home/End, focus survival). The host owns a11y for a custom bar.\n  const tablist = createRovingTablist(list, {\n    onSelect: (index) => options.onSelect(records[index].id),\n  });\n\n  // Toggle the tab-strip edge fades from scroll position. RTL reports a negative\n  // scrollLeft, so normalize against the max offset rather than trusting sign.\n  // Per-edge fade classes are gated by config.\n  const updateTabFades = (el: HTMLElement) => {\n    const max = el.scrollWidth - el.clientWidth;\n    const overflow = max > 1;\n    const offset = Math.abs(el.scrollLeft);\n    el.classList.toggle(\n      \"persona-artifact-tab-fade-start\",\n      fadeStartEnabled && overflow && offset > 1\n    );\n    el.classList.toggle(\n      \"persona-artifact-tab-fade-end\",\n      fadeEndEnabled && overflow && offset < max - 1\n    );\n  };\n  // rAF-throttle the scroll-driven fade recompute (falls back to setTimeout).\n  let tabFadeRaf = 0;\n  const scheduleTabFades = () => {\n    if (tabFadeRaf) return;\n    const run = () => {\n      tabFadeRaf = 0;\n      updateTabFades(list);\n    };\n    tabFadeRaf =\n      typeof requestAnimationFrame === \"function\"\n        ? requestAnimationFrame(run)\n        : (setTimeout(run, 0) as unknown as number);\n  };\n  list.addEventListener(\"scroll\", () => scheduleTabFades(), { passive: true });\n  // Recompute on mid-stream tab additions and pane resizes.\n  if (typeof ResizeObserver !== \"undefined\") {\n    new ResizeObserver(() => updateTabFades(list)).observe(list);\n  }\n\n  const content = createElement(\n    \"div\",\n    \"persona-artifact-content persona-flex-1 persona-min-h-0 persona-overflow-y-auto persona-p-3\"\n  );\n  if (panePadding) {\n    for (const el of [list, customBarMount]) {\n      el.style.paddingLeft = panePadding;\n      el.style.paddingRight = panePadding;\n    }\n    content.style.padding = panePadding;\n  }\n\n  shell.appendChild(toolbar);\n  shell.appendChild(list);\n  shell.appendChild(customBarMount);\n  shell.appendChild(content);\n\n  let records: PersonaArtifactRecord[] = [];\n  let selectedId: string | null = null;\n  let mobileOpen = false;\n  // Lazy-render gate. `visible` reflects the host's explicit setVisible() signal\n  // (never inferred from layout/offsetParent, so class-hiding + jsdom behave the\n  // same). It defaults to true so the pane renders eagerly when constructed\n  // directly (its historical behavior, and the path pane tests exercise); ui.ts\n  // always calls setVisible() before update() so the real widget drives it. When\n  // hidden, update() records state and sets `dirty` instead of rendering; the\n  // next reveal flushes the current state.\n  let visible = true;\n  let dirty = false;\n  // Track the last tab we auto-scrolled to so we only nudge the strip when the\n  // selection actually changes (not on every streaming re-render), which would\n  // otherwise fight a user who has scrolled the strip manually.\n  let lastScrolledTabId: string | null = null;\n  // Signature of the last renderTabBar invocation (record ids + selection) so a\n  // streaming re-render with an unchanged bar does not tear down the host bar.\n  let customBarSig = \"\";\n\n  // Shared preview body renderer (artifact-preview.ts). One handle serves the\n  // whole content area; it is updated with whichever record is selected and\n  // internally reuses the file-preview iframe across idle re-renders.\n  let preview: ArtifactPreviewBodyHandle | null = null;\n  let fileToggleMounted = false;\n  const resolveViewMode = (rec: PersonaArtifactRecord): \"rendered\" | \"source\" => {\n    const isFile = rec.artifactType === \"markdown\" && Boolean(rec.file);\n    // File artifacts honor the toggle in every toolbar preset; plain markdown\n    // only has a source view in the document toolbar preset.\n    if (isFile) return viewMode;\n    return documentChrome ? viewMode : \"rendered\";\n  };\n  // For the default (non-document) toolbar, mount the rendered/source toggle only\n  // while a previewable file artifact is selected. The document toolbar already\n  // carries the toggle permanently, so it is mounted once at build time. Here we\n  // add/remove the group from the toolbar as the selection gains or loses a\n  // previewable file (the toggle group owns its own buttons and aria-pressed state).\n  const updateFileToggleVisibility = (previewable: boolean) => {\n    if (documentChrome) return;\n    if (previewable && !fileToggleMounted) {\n      toolbar.insertBefore(viewToggle.element, titleEl);\n      fileToggleMounted = true;\n    } else if (!previewable && fileToggleMounted) {\n      viewToggle.element.remove();\n      fileToggleMounted = false;\n    }\n  };\n\n  // Selection fallback shared with render(): explicit selection, else last record.\n  const currentSelectedRecord = (): PersonaArtifactRecord | undefined =>\n    (selectedId && records.find((x) => x.id === selectedId)) || records[records.length - 1];\n\n  // Rebuild the custom-action buttons. Called from render() (so per-artifact\n  // visible() gates re-evaluate when the selection changes) and from\n  // setCustomActions (so live config updates apply). The onClick context is\n  // resolved at click time, not here, so it reflects the current content.\n  const renderCustomActions = () => {\n    const ctx = artifactRecordActionContext(currentSelectedRecord());\n    if (!ctx) {\n      // No records: no meaningful context, so render nothing.\n      customActions.replaceChildren();\n      return;\n    }\n    const buttons = customActionList\n      .filter((action) => action.visible === undefined || action.visible(ctx))\n      .map((action) =>\n        buildArtifactActionButton(action, {\n          documentChrome,\n          onClick: () => {\n            const c = artifactRecordActionContext(currentSelectedRecord());\n            if (!c) return;\n            try {\n              void Promise.resolve(action.onClick(c)).catch(() => {});\n            } catch {\n              /* ignore */\n            }\n          },\n        })\n      );\n    customActions.replaceChildren(...buttons);\n  };\n\n  const render = () => {\n    renderCustomActions();\n    const hideTabs = documentChrome && records.length <= 1;\n    const useCustom = Boolean(currentRenderTabBar);\n    // Show the active bar and hide the other; both hide in the ≤1 document case.\n    list.classList.toggle(\"persona-hidden\", hideTabs || useCustom);\n    customBarMount.classList.toggle(\"persona-hidden\", hideTabs || !useCustom);\n\n    if (useCustom && currentRenderTabBar) {\n      // Signature-gate so unrelated streaming re-renders don't tear down the\n      // host bar and lose its internal state or focus.\n      const sig = records.map((r) => r.id).join(\"|\") + \" \" + (selectedId ?? \"\");\n      if (sig !== customBarSig) {\n        customBarSig = sig;\n        const bar = currentRenderTabBar({\n          records,\n          selectedId,\n          onSelect: options.onSelect,\n        });\n        // A renderer may return the SAME element across invocations to keep its\n        // internal state (e.g. a roving tablist's keyboard focus on the selected\n        // tab). Only remount when the node actually changed; replaceChildren\n        // detaches the subtree, which would blur a focused tab and break arrow\n        // nav on every selection change.\n        if (customBarMount.firstElementChild !== bar) {\n          customBarMount.replaceChildren(bar);\n        }\n      }\n    } else {\n      // Capture focus before replacing tab DOM so the controller restores the\n      // roving stop after the rebuild (keyboard nav dies otherwise).\n      tablist.beforeRender();\n      list.replaceChildren();\n      const tabEls: HTMLButtonElement[] = [];\n      let selectedIndex = -1;\n      for (const [index, r] of records.entries()) {\n        const tab = createElement(\n          \"button\",\n          \"persona-artifact-tab persona-shrink-0 persona-rounded-lg persona-px-2 persona-py-1 persona-text-xs persona-border persona-border-transparent persona-text-persona-primary\"\n        );\n        tab.type = \"button\";\n        // Prefer the file basename over the full path so tabs stay readable\n        // (matches the toolbar title); keep the full path/title in a tooltip.\n        const fileMeta = r.artifactType === \"markdown\" ? r.file : undefined;\n        const label = fileMeta ? basenameOf(fileMeta.path) : r.title || r.id.slice(0, 8);\n        const tooltip = fileMeta?.path || r.title || label;\n        tab.textContent = label;\n        tab.title = tooltip;\n        tab.setAttribute(\"aria-label\", tooltip);\n        if (r.id === selectedId) {\n          tab.classList.add(\"persona-bg-persona-container\", \"persona-border-persona-border\");\n          selectedIndex = index;\n        }\n        tab.addEventListener(\"click\", () => options.onSelect(r.id));\n        list.appendChild(tab);\n        tabEls.push(tab);\n      }\n      // The controller owns role/aria-selected/roving tabindex, keyboard nav,\n      // and focus restoration across this rebuild.\n      tablist.render(tabEls, selectedIndex);\n\n      // Keep the selected tab visible when the selection changes (e.g. a new\n      // artifact streams in and auto-selects). `inline: \"nearest\"` is a no-op\n      // when the tab is already visible, so this never yanks the strip needlessly.\n      if (selectedIndex >= 0 && selectedId !== lastScrolledTabId) {\n        lastScrolledTabId = selectedId;\n        const activeTab = tabEls[selectedIndex];\n        if (typeof activeTab.scrollIntoView === \"function\") {\n          activeTab.scrollIntoView({ block: \"nearest\", inline: \"nearest\" });\n        }\n      }\n\n      // Recompute the edge fades after tabs and the selection scroll settle.\n      updateTabFades(list);\n    }\n\n    const sel =\n      (selectedId && records.find((x) => x.id === selectedId)) ||\n      records[records.length - 1];\n    if (!sel) {\n      content.replaceChildren();\n      preview = null;\n      updateFileToggleVisibility(false);\n      return;\n    }\n\n    const selFile = sel.artifactType === \"markdown\" ? sel.file : undefined;\n\n    // Expose the rendered/source toggle for previewable file artifacts even\n    // outside the document toolbar preset.\n    updateFileToggleVisibility(Boolean(selFile));\n\n    if (documentChrome) {\n      const kind = selFile\n        ? fileTypeLabel(selFile)\n        : sel.artifactType === \"markdown\"\n          ? \"MD\"\n          : sel.component ?? \"Component\";\n      const rawTitle = (sel.title || \"Document\").trim();\n      const baseTitle = selFile\n        ? basenameOf(selFile.path)\n        : rawTitle.replace(/\\s*·\\s*MD\\s*$/i, \"\").trim() || \"Document\";\n      centerTitle.textContent = `${baseTitle} · ${kind}`;\n    } else {\n      titleEl.textContent = selFile ? basenameOf(selFile.path) : toolbarTitle;\n    }\n\n    // Delegate the body to the shared preview renderer. Keep the handle's\n    // element attached across updates: re-attaching would reload a live\n    // file-preview iframe.\n    if (!preview) {\n      preview = renderArtifactPreviewBody(sel, { config, resolveViewMode });\n      content.replaceChildren(preview.el);\n    } else {\n      if (preview.el.parentElement !== content) {\n        content.replaceChildren(preview.el);\n      }\n      preview.update(sel);\n    }\n    // Source view fills the pane edge-to-edge (gutter flush left, no inset),\n    // so drop the content padding whenever the body rendered a code pre.\n    // Checked after the render rather than re-deriving the pre/markdown/iframe\n    // decision here — renderArtifactPreviewBody owns that branching.\n    content.classList.toggle(\n      \"persona-artifact-content-flush\",\n      Boolean(content.querySelector(\".persona-code-pre\"))\n    );\n  };\n\n  const applyLayoutVisibility = () => {\n    const has = records.length > 0;\n    shell.classList.toggle(\"persona-hidden\", !has);\n    if (backdrop) {\n      const root =\n        typeof shell.closest === \"function\" ? shell.closest(\"[data-persona-root]\") : null;\n      const narrowHost = root?.classList.contains(\"persona-artifact-narrow-host\") ?? false;\n      const isMobile =\n        narrowHost ||\n        (typeof window !== \"undefined\" && window.matchMedia(\"(max-width: 640px)\").matches);\n      if (has && isMobile && mobileOpen) {\n        backdrop.classList.remove(\"persona-hidden\");\n        shell.classList.add(\"persona-artifact-drawer-open\");\n      } else if (!isMobile) {\n        backdrop.classList.add(\"persona-hidden\");\n        shell.classList.remove(\"persona-artifact-drawer-open\");\n      } else {\n        // isMobile && !(has && mobileOpen): e.g. dismissed drawer: keep closed chrome in sync\n        backdrop.classList.add(\"persona-hidden\");\n        shell.classList.remove(\"persona-artifact-drawer-open\");\n      }\n    }\n  };\n\n  // Build/refresh the visible DOM (tab strip, title, preview body) plus the\n  // layout chrome. Gated behind `visible` so we never create a preview iframe\n  // for a pane the user isn't looking at.\n  const flush = () => {\n    dirty = false;\n    render();\n    applyLayoutVisibility();\n  };\n\n  return {\n    element: shell,\n    backdrop,\n    update(state: { artifacts: PersonaArtifactRecord[]; selectedId: string | null }) {\n      records = state.artifacts;\n      selectedId =\n        state.selectedId ??\n        state.artifacts[state.artifacts.length - 1]?.id ??\n        null;\n      if (records.length > 0) {\n        mobileOpen = true;\n      }\n      // Record the latest state cheaply; only build the preview DOM when shown.\n      // While hidden (inline/card display modes), rendering here would spawn a\n      // second sandboxed srcdoc iframe alongside the inline transcript preview,\n      // executing artifact scripts twice. Defer the whole render() to reveal.\n      dirty = true;\n      if (visible) flush();\n    },\n    setMobileOpen(open: boolean) {\n      mobileOpen = open;\n      if (!open && backdrop) {\n        backdrop.classList.add(\"persona-hidden\");\n        shell.classList.remove(\"persona-artifact-drawer-open\");\n      } else {\n        applyLayoutVisibility();\n      }\n    },\n    setExpanded(expanded: boolean) {\n      // Swap the icon (the state signal) and update the accessible label. We\n      // deliberately avoid aria-pressed here: the icon-btn [aria-pressed] CSS\n      // would add unwanted active styling.\n      const svg = renderLucideIcon(expanded ? \"minimize\" : \"maximize\", 16, \"currentColor\", 2);\n      if (svg) expandBtn.replaceChildren(svg);\n      const label = expanded ? \"Collapse artifacts panel\" : \"Expand artifacts panel\";\n      expandBtn.setAttribute(\"aria-label\", label);\n      expandBtn.title = label;\n    },\n    setExpandToggleVisible(visible: boolean) {\n      expandBtn.classList.toggle(\"persona-hidden\", !visible);\n    },\n    setCopyButtonVisible(visible: boolean) {\n      if (documentChrome) return;\n      copyBtn.classList.toggle(\"persona-hidden\", !visible);\n    },\n    setCustomActions(actions: PersonaArtifactCustomAction[]) {\n      customActionList = actions;\n      renderCustomActions();\n    },\n    setTabFade(cfg: boolean | { start?: boolean; end?: boolean } | undefined) {\n      const resolved = resolveFade(cfg);\n      if (resolved.start === fadeStartEnabled && resolved.end === fadeEndEnabled) {\n        return;\n      }\n      fadeStartEnabled = resolved.start;\n      fadeEndEnabled = resolved.end;\n      updateTabFades(list);\n    },\n    setRenderTabBar(fn) {\n      if (fn === currentRenderTabBar) return;\n      currentRenderTabBar = fn;\n      // Force a custom-bar rebuild on the next render and re-render now so the\n      // switch (built-in <-> custom) applies live.\n      customBarSig = \"\";\n      render();\n    },\n    setVisible(next: boolean) {\n      if (next === visible) return;\n      visible = next;\n      // Reveal after lazy-skipped updates: render the CURRENT recorded state.\n      // Hiding never tears down an already-rendered preview — re-appending the\n      // iframe on re-open would reload it (a hard invariant; see the reuse\n      // comments in artifact-preview.ts). Laziness only blocks building NEW\n      // previews while hidden; an already-mounted one stays put.\n      if (next && dirty) flush();\n    }\n  };\n}\n","import type { AgentWidgetConfig, PersonaArtifactKind } from \"../types\";\n\nexport function artifactsSidebarEnabled(config: AgentWidgetConfig | undefined): boolean {\n  return config?.features?.artifacts?.enabled === true;\n}\n\nexport function artifactKindAllowedByFeature(\n  config: AgentWidgetConfig | undefined,\n  kind: PersonaArtifactKind\n): boolean {\n  const allowed = config?.features?.artifacts?.allowedTypes;\n  if (!allowed?.length) return true;\n  return allowed.includes(kind);\n}\n\n/** Optional custom border on artifact pane via CSS vars + root classes. */\nexport function applyArtifactPaneBorderTheme(mount: HTMLElement, config: AgentWidgetConfig): void {\n  mount.classList.remove(\"persona-artifact-border-full\", \"persona-artifact-border-left\");\n  mount.style.removeProperty(\"--persona-artifact-pane-border\");\n  mount.style.removeProperty(\"--persona-artifact-pane-border-left\");\n  if (!artifactsSidebarEnabled(config)) return;\n\n  const l = config.features?.artifacts?.layout;\n  const full = l?.paneBorder?.trim();\n  const left = l?.paneBorderLeft?.trim();\n  if (full) {\n    mount.classList.add(\"persona-artifact-border-full\");\n    mount.style.setProperty(\"--persona-artifact-pane-border\", full);\n  } else if (left) {\n    mount.classList.add(\"persona-artifact-border-left\");\n    mount.style.setProperty(\"--persona-artifact-pane-border-left\", left);\n  }\n}\n\n/** Set CSS variables on the widget root for artifact split/pane sizing. Clears when artifacts disabled. */\nfunction clearDocumentToolbarLayoutVars(mount: HTMLElement): void {\n  mount.style.removeProperty(\"--persona-artifact-doc-toolbar-icon-color\");\n  mount.style.removeProperty(\"--persona-artifact-doc-toggle-active-bg\");\n  mount.style.removeProperty(\"--persona-artifact-doc-toggle-active-border\");\n}\n\nconst ARTIFACT_APPEARANCE_MODES = [\"panel\", \"seamless\", \"detached\"] as const;\ntype ArtifactAppearance = (typeof ARTIFACT_APPEARANCE_MODES)[number];\n\n/** Resolve the effective pane appearance, applying the detached-panel coordinated default. */\nfunction resolveArtifactPaneAppearance(config: AgentWidgetConfig): ArtifactAppearance {\n  const raw = config.features?.artifacts?.layout?.paneAppearance;\n  if (raw && (ARTIFACT_APPEARANCE_MODES as readonly string[]).includes(raw)) {\n    return raw as ArtifactAppearance;\n  }\n  if (raw) return \"panel\";\n  // Coordinated default: a detached panel gets a detached pane unless overridden.\n  return config.launcher?.detachedPanel ? \"detached\" : \"panel\";\n}\n\n/** True when the effective pane appearance resolves to \"detached\" (explicit or coordinated default). */\nexport function isArtifactPaneAppearanceDetached(config: AgentWidgetConfig | undefined): boolean {\n  if (!config || !artifactsSidebarEnabled(config)) return false;\n  return resolveArtifactPaneAppearance(config) === \"detached\";\n}\n\nexport function applyArtifactLayoutCssVars(mount: HTMLElement, config: AgentWidgetConfig): void {\n  if (!artifactsSidebarEnabled(config)) {\n    mount.style.removeProperty(\"--persona-artifact-split-gap\");\n    mount.style.removeProperty(\"--persona-artifact-pane-width\");\n    mount.style.removeProperty(\"--persona-artifact-pane-max-width\");\n    mount.style.removeProperty(\"--persona-artifact-pane-min-width\");\n    mount.style.removeProperty(\"--persona-artifact-pane-bg\");\n    mount.style.removeProperty(\"--persona-artifact-pane-padding\");\n    clearDocumentToolbarLayoutVars(mount);\n    applyArtifactPaneBorderTheme(mount, config);\n    return;\n  }\n  const l = config.features?.artifacts?.layout;\n  // Detached shows the canvas between columns; panel/seamless weld at gap 0.\n  const gapDefault =\n    resolveArtifactPaneAppearance(config) === \"detached\"\n      ? \"var(--persona-panel-inset)\"\n      : \"0\";\n  mount.style.setProperty(\"--persona-artifact-split-gap\", l?.splitGap ?? gapDefault);\n  mount.style.setProperty(\"--persona-artifact-pane-width\", l?.paneWidth ?? \"40%\");\n  mount.style.setProperty(\"--persona-artifact-pane-max-width\", l?.paneMaxWidth ?? \"28rem\");\n  if (l?.paneMinWidth) {\n    mount.style.setProperty(\"--persona-artifact-pane-min-width\", l.paneMinWidth);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-pane-min-width\");\n  }\n  const paneBg = l?.paneBackground?.trim();\n  if (paneBg) {\n    mount.style.setProperty(\"--persona-artifact-pane-bg\", paneBg);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-pane-bg\");\n  }\n  const panePad = l?.panePadding?.trim();\n  if (panePad) {\n    mount.style.setProperty(\"--persona-artifact-pane-padding\", panePad);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-pane-padding\");\n  }\n\n  const iconColor = l?.documentToolbarIconColor?.trim();\n  if (iconColor) {\n    mount.style.setProperty(\"--persona-artifact-doc-toolbar-icon-color\", iconColor);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-doc-toolbar-icon-color\");\n  }\n  const toggleBg = l?.documentToolbarToggleActiveBackground?.trim();\n  if (toggleBg) {\n    mount.style.setProperty(\"--persona-artifact-doc-toggle-active-bg\", toggleBg);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-doc-toggle-active-bg\");\n  }\n  const toggleBorder = l?.documentToolbarToggleActiveBorderColor?.trim();\n  if (toggleBorder) {\n    mount.style.setProperty(\"--persona-artifact-doc-toggle-active-border\", toggleBorder);\n  } else {\n    mount.style.removeProperty(\"--persona-artifact-doc-toggle-active-border\");\n  }\n\n  applyArtifactPaneBorderTheme(mount, config);\n}\n\n/** Toggle root classes for artifact pane appearance, radius, and shadow vars. */\nexport function applyArtifactPaneAppearance(mount: HTMLElement, config: AgentWidgetConfig): void {\n  for (const m of ARTIFACT_APPEARANCE_MODES) {\n    mount.classList.remove(`persona-artifact-appearance-${m}`);\n  }\n  mount.style.removeProperty(\"--persona-artifact-pane-radius\");\n  mount.style.removeProperty(\"--persona-artifact-pane-shadow\");\n  mount.style.removeProperty(\"--persona-artifact-chat-shadow\");\n  if (!artifactsSidebarEnabled(config)) return;\n\n  const layout = config.features?.artifacts?.layout;\n  const mode = resolveArtifactPaneAppearance(config);\n  mount.classList.add(`persona-artifact-appearance-${mode}`);\n\n  const radius = layout?.paneBorderRadius?.trim();\n  if (radius) {\n    mount.style.setProperty(\"--persona-artifact-pane-radius\", radius);\n  }\n\n  const shadow = layout?.paneShadow?.trim();\n  if (shadow) {\n    mount.style.setProperty(\"--persona-artifact-pane-shadow\", shadow);\n  }\n\n  // Chat card gains its own front shadow lookup so a detached split can flatten\n  // the chat column while the pane stays raised. Unset falls back to the pane chain.\n  const chatShadow = layout?.chatShadow?.trim();\n  if (chatShadow) {\n    mount.style.setProperty(\"--persona-artifact-chat-shadow\", chatShadow);\n  }\n\n  // Panel/seamless splits weld by default; `unifiedSplitChrome` is a deprecated\n  // no-op. The welded outer-right radius (`--persona-artifact-welded-outer-radius`)\n  // is owned by ui.ts syncPanelChrome so it derives from the same resolved panel\n  // radius as the chat card; `unifiedSplitOuterRadius` / `paneBorderRadius` win there.\n}\n\n/** Widen floating panel when artifacts show (default true); `false` opts out. */\nexport function shouldExpandLauncherForArtifacts(\n  config: AgentWidgetConfig,\n  launcherEnabled: boolean\n): boolean {\n  if (!launcherEnabled || !artifactsSidebarEnabled(config)) return false;\n  return config.features?.artifacts?.layout?.expandLauncherPanelWhenOpen !== false;\n}\n","/** Minimum width (px) reserved for the chat column while resizing the artifact pane. */\nexport const ARTIFACT_RESIZE_CHAT_MIN_PX = 200;\n\n/** Default minimum width (px) for the artifact column when `resizableMinWidth` is unset. */\nexport const ARTIFACT_RESIZE_PANE_MIN_DEFAULT_PX = 200;\n\n/** Parse a `NNpx` string; returns `fallback` if missing or invalid. */\nexport function parseArtifactResizePx(input: string | undefined, fallback: number): number {\n  if (!input?.trim()) return fallback;\n  const m = /^(\\d+(?:\\.\\d+)?)px\\s*$/i.exec(input.trim());\n  if (!m) return fallback;\n  return Math.max(0, Number(m[1]));\n}\n\n/** Optional max from config: only valid `px` strings apply; otherwise no extra cap. */\nexport function parseArtifactResizeMaxPxOptional(input: string | undefined): number | null {\n  if (!input?.trim()) return null;\n  const m = /^(\\d+(?:\\.\\d+)?)px\\s*$/i.exec(input.trim());\n  if (!m) return null;\n  return Math.max(0, Number(m[1]));\n}\n\nexport function clampArtifactPaneWidth(widthPx: number, minPx: number, maxPx: number): number {\n  if (maxPx < minPx) return minPx;\n  return Math.min(maxPx, Math.max(minPx, widthPx));\n}\n\n/**\n * Upper bound for artifact width (px) from split row geometry: leave room for chat min, two flex gaps, handle.\n */\nexport function maxArtifactWidthFromSplit(\n  splitWidthPx: number,\n  gapPx: number,\n  handleWidthPx: number,\n  chatMinPx: number\n): number {\n  const raw = splitWidthPx - chatMinPx - 2 * gapPx - handleWidthPx;\n  return Math.max(0, raw);\n}\n\n/** Read the first gap value from computed `gap` (e.g. `8px` or `8px 8px`). */\nexport function readFlexGapPx(splitRoot: HTMLElement, win: Window): number {\n  const g = win.getComputedStyle(splitRoot).gap || \"0px\";\n  const first = g.trim().split(/\\s+/)[0] ?? \"0px\";\n  const m = /^([\\d.]+)px$/i.exec(first);\n  if (m) return Number(m[1]);\n  const m2 = /^([\\d.]+)/.exec(first);\n  return m2 ? Number(m2[1]) : 8;\n}\n\nexport function resolveArtifactPaneWidthPx(\n  candidatePx: number,\n  splitWidthPx: number,\n  gapPx: number,\n  handleWidthPx: number,\n  resizableMinWidth?: string,\n  resizableMaxWidth?: string\n): number {\n  const minPx = parseArtifactResizePx(resizableMinWidth, ARTIFACT_RESIZE_PANE_MIN_DEFAULT_PX);\n  let maxPx = maxArtifactWidthFromSplit(splitWidthPx, gapPx, handleWidthPx, ARTIFACT_RESIZE_CHAT_MIN_PX);\n  maxPx = Math.max(minPx, maxPx);\n  const cap = parseArtifactResizeMaxPxOptional(resizableMaxWidth);\n  if (cap !== null) {\n    maxPx = Math.min(maxPx, cap);\n  }\n  return clampArtifactPaneWidth(candidatePx, minPx, maxPx);\n}\n","import { createElement } from \"../utils/dom\";\nimport { AgentWidgetMessage, AgentWidgetConfig } from \"../types\";\nimport { AgentWidgetSession } from \"../session\";\n\nexport const formDefinitions: Record<\n  string,\n  {\n    title: string;\n    description?: string;\n    fields: Array<{\n      name: string;\n      label: string;\n      placeholder?: string;\n      type?: \"text\" | \"email\" | \"textarea\";\n      required?: boolean;\n    }>;\n    submitLabel?: string;\n  }\n> = {\n  init: {\n    title: \"Schedule a Demo\",\n    description: \"Share the basics and we'll follow up with a confirmation.\",\n    fields: [\n      { name: \"name\", label: \"Full name\", placeholder: \"Jane Doe\", required: true },\n      { name: \"email\", label: \"Work email\", placeholder: \"jane@example.com\", type: \"email\", required: true },\n      { name: \"notes\", label: \"What would you like to cover?\", type: \"textarea\" }\n    ],\n    submitLabel: \"Submit details\"\n  },\n  followup: {\n    title: \"Additional Information\",\n    description: \"Provide any extra details to tailor the next steps.\",\n    fields: [\n      { name: \"company\", label: \"Company\", placeholder: \"Acme Inc.\" },\n      { name: \"context\", label: \"Context\", type: \"textarea\", placeholder: \"Share more about your use case\" }\n    ],\n    submitLabel: \"Send\"\n  }\n};\n\nexport const enhanceWithForms = (\n  bubble: HTMLElement,\n  message: AgentWidgetMessage,\n  config: AgentWidgetConfig,\n  session: AgentWidgetSession\n) => {\n  const placeholders = bubble.querySelectorAll<HTMLElement>(\"[data-tv-form]\");\n  if (placeholders.length) {\n    placeholders.forEach((placeholder) => {\n      if (placeholder.dataset.enhanced === \"true\") return;\n      const type = placeholder.dataset.tvForm ?? \"init\";\n      placeholder.dataset.enhanced = \"true\";\n\n      const definition = formDefinitions[type] ?? formDefinitions.init;\n      placeholder.classList.add(\"persona-form-card\", \"persona-space-y-4\");\n\n      const heading = createElement(\"div\", \"persona-space-y-1\");\n      const title = createElement(\n        \"h3\",\n        \"persona-text-base persona-font-semibold persona-text-persona-primary\"\n      );\n      title.textContent = definition.title;\n      heading.appendChild(title);\n      if (definition.description) {\n        const desc = createElement(\n          \"p\",\n          \"persona-text-sm persona-text-persona-muted\"\n        );\n        desc.textContent = definition.description;\n        heading.appendChild(desc);\n      }\n\n      const form = document.createElement(\"form\");\n      form.className = \"persona-form-grid persona-space-y-3\";\n\n      definition.fields.forEach((field) => {\n        const group = createElement(\"label\", \"persona-form-field persona-flex persona-flex-col persona-gap-1\");\n        group.htmlFor = `${message.id}-${type}-${field.name}`;\n        const label = createElement(\"span\", \"persona-text-xs persona-font-medium persona-text-persona-muted\");\n        label.textContent = field.label;\n        group.appendChild(label);\n\n        const inputType = field.type ?? \"text\";\n        let control: HTMLInputElement | HTMLTextAreaElement;\n        if (inputType === \"textarea\") {\n          control = document.createElement(\"textarea\");\n          control.rows = 3;\n        } else {\n          control = document.createElement(\"input\");\n          control.type = inputType;\n        }\n        control.className =\n          \"persona-rounded-xl persona-border persona-border-gray-200 persona-bg-white persona-px-3 persona-py-2 persona-text-sm persona-text-persona-primary focus:persona-outline-none focus:persona-border-persona-primary\";\n        control.id = `${message.id}-${type}-${field.name}`;\n        control.name = field.name;\n        control.placeholder = field.placeholder ?? \"\";\n        if (field.required) {\n          control.required = true;\n        }\n        group.appendChild(control);\n        form.appendChild(group);\n      });\n\n      const actions = createElement(\n        \"div\",\n        \"persona-flex persona-items-center persona-justify-between persona-gap-2\"\n      );\n      const status = createElement(\n        \"div\",\n        \"persona-text-xs persona-text-persona-muted persona-min-h-[1.5rem]\"\n      );\n      const submit = createElement(\n        \"button\",\n        \"persona-inline-flex persona-items-center persona-rounded-full persona-bg-persona-primary persona-px-4 persona-py-2 persona-text-sm persona-font-semibold persona-text-white disabled:persona-opacity-60 persona-cursor-pointer\"\n      ) as HTMLButtonElement;\n      submit.type = \"submit\";\n      submit.textContent = definition.submitLabel ?? \"Submit\";\n      actions.appendChild(status);\n      actions.appendChild(submit);\n      form.appendChild(actions);\n\n      placeholder.replaceChildren(heading, form);\n\n      form.addEventListener(\"submit\", async (event) => {\n        event.preventDefault();\n        const formEndpoint = config.formEndpoint ?? \"/form\";\n        const formData = new FormData(form as HTMLFormElement);\n        const payload: Record<string, unknown> = {};\n        formData.forEach((value, key) => {\n          payload[key] = value;\n        });\n        payload[\"type\"] = type;\n\n        submit.disabled = true;\n        status.textContent = \"Submitting…\";\n\n        try {\n          const response = await fetch(formEndpoint, {\n            method: \"POST\",\n            headers: {\n              \"Content-Type\": \"application/json\"\n            },\n            body: JSON.stringify(payload)\n          });\n          if (!response.ok) {\n            throw new Error(`Form submission failed (${response.status})`);\n          }\n          const data = await response.json();\n          status.textContent = data.message ?? \"Thanks! We'll be in touch soon.\";\n          if (data.success && data.nextPrompt) {\n            await session.sendMessage(String(data.nextPrompt));\n          }\n        } catch (error) {\n          status.textContent =\n            error instanceof Error ? error.message : \"Something went wrong. Please try again.\";\n        } finally {\n          submit.disabled = false;\n        }\n      });\n    });\n  }\n};\n\n\n\n\n\n\n\n\n","import { AgentWidgetPlugin } from \"./types\";\n\nclass PluginRegistry {\n  private plugins: Map<string, AgentWidgetPlugin> = new Map();\n\n  /**\n   * Register a plugin\n   */\n  register(plugin: AgentWidgetPlugin): void {\n    if (this.plugins.has(plugin.id)) {\n      console.warn(`Plugin \"${plugin.id}\" is already registered. Overwriting.`);\n    }\n\n    this.plugins.set(plugin.id, plugin);\n    plugin.onRegister?.();\n  }\n\n  /**\n   * Unregister a plugin\n   */\n  unregister(pluginId: string): void {\n    const plugin = this.plugins.get(pluginId);\n    if (plugin) {\n      plugin.onUnregister?.();\n      this.plugins.delete(pluginId);\n    }\n  }\n\n  /**\n   * Get all plugins sorted by priority\n   */\n  getAll(): AgentWidgetPlugin[] {\n    return Array.from(this.plugins.values()).sort(\n      (a, b) => (b.priority ?? 0) - (a.priority ?? 0)\n    );\n  }\n\n  /**\n   * Get plugins for a specific instance (from config)\n   * Merges instance plugins with globally registered plugins\n   */\n  getForInstance(instancePlugins?: AgentWidgetPlugin[]): AgentWidgetPlugin[] {\n    const allPlugins = this.getAll();\n\n    if (!instancePlugins || instancePlugins.length === 0) {\n      return allPlugins;\n    }\n\n    // Merge instance plugins with global plugins\n    // Instance plugins override global plugins with the same ID\n    const instanceIds = new Set(instancePlugins.map(p => p.id));\n    const merged = [\n      ...allPlugins.filter(p => !instanceIds.has(p.id)),\n      ...instancePlugins\n    ];\n\n    return merged.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));\n  }\n\n  /**\n   * Clear all plugins\n   */\n  clear(): void {\n    this.plugins.forEach(plugin => plugin.onUnregister?.());\n    this.plugins.clear();\n  }\n}\n\nexport const pluginRegistry = new PluginRegistry();\n\n\n\n\n\n\n\n\n","type Handler<T> = (payload: T) => void;\n\nexport type EventUnsubscribe = () => void;\n\nexport const createEventBus = <EventMap extends Record<string, any>>() => {\n  const listeners = new Map<keyof EventMap, Set<Handler<any>>>();\n\n  const on = <K extends keyof EventMap>(\n    event: K,\n    handler: Handler<EventMap[K]>\n  ): EventUnsubscribe => {\n    if (!listeners.has(event)) {\n      listeners.set(event, new Set());\n    }\n    listeners.get(event)!.add(handler as Handler<any>);\n    return () => off(event, handler);\n  };\n\n  const off = <K extends keyof EventMap>(\n    event: K,\n    handler: Handler<EventMap[K]>\n  ) => {\n    listeners.get(event)?.delete(handler as Handler<any>);\n  };\n\n  const emit = <K extends keyof EventMap>(event: K, payload: EventMap[K]) => {\n    listeners.get(event)?.forEach((handler) => {\n      try {\n        handler(payload);\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Event handler error:\", error);\n        }\n      }\n    });\n  };\n\n  return { on, off, emit };\n};\n\n","import type {\n  AgentWidgetActionContext,\n  AgentWidgetActionEventPayload,\n  AgentWidgetActionHandler,\n  AgentWidgetActionHandlerResult,\n  AgentWidgetActionParser,\n  AgentWidgetParsedAction,\n  AgentWidgetControllerEventMap,\n  AgentWidgetMessage\n} from \"../types\";\n\ntype ActionManagerProcessContext = {\n  text: string;\n  message: AgentWidgetMessage;\n  streaming: boolean;\n  raw?: string;\n};\n\ntype ActionManagerOptions = {\n  parsers: AgentWidgetActionParser[];\n  handlers: AgentWidgetActionHandler[];\n  getSessionMetadata: () => Record<string, unknown>;\n  updateSessionMetadata: (\n    updater: (prev: Record<string, unknown>) => Record<string, unknown>\n  ) => void;\n  emit: <K extends keyof AgentWidgetControllerEventMap>(\n    event: K,\n    payload: AgentWidgetControllerEventMap[K]\n  ) => void;\n  documentRef: Document | null;\n};\n\nconst stripCodeFence = (value: string) => {\n  const match = value.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n  return match ? match[1] : value;\n};\n\nconst extractJsonObject = (value: string) => {\n  const trimmed = value.trim();\n  const start = trimmed.indexOf(\"{\");\n  if (start === -1) return null;\n\n  let depth = 0;\n  for (let i = start; i < trimmed.length; i += 1) {\n    const char = trimmed[i];\n    if (char === \"{\") depth += 1;\n    if (char === \"}\") {\n      depth -= 1;\n      if (depth === 0) {\n        return trimmed.slice(start, i + 1);\n      }\n    }\n  }\n  return null;\n};\n\nexport const defaultJsonActionParser: AgentWidgetActionParser = ({ text }) => {\n  if (!text) return null;\n  if (!text.includes(\"{\")) return null;\n\n  try {\n    const withoutFence = stripCodeFence(text);\n    const jsonBody = extractJsonObject(withoutFence);\n    if (!jsonBody) return null;\n    const parsed = JSON.parse(jsonBody);\n    if (!parsed || typeof parsed !== \"object\" || !parsed.action) {\n      return null;\n    }\n    const { action, ...payload } = parsed;\n    return {\n      type: String(action),\n      payload,\n      raw: parsed\n    };\n  } catch {\n    return null;\n  }\n};\n\nconst asString = (value: unknown) =>\n  typeof value === \"string\" ? value : value == null ? \"\" : String(value);\n\nexport const defaultActionHandlers: Record<\n  string,\n  AgentWidgetActionHandler\n> = {\n  message: (action) => {\n    if (action.type !== \"message\") return;\n    const text = asString((action.payload as Record<string, unknown>).text);\n    return {\n      handled: true,\n      displayText: text\n    };\n  },\n  messageAndClick: (action, context) => {\n    if (action.type !== \"message_and_click\") return;\n    const payload = action.payload as Record<string, unknown>;\n    const selector = asString(payload.element);\n    if (selector && context.document?.querySelector) {\n      const element = context.document.querySelector<HTMLElement>(selector);\n      if (element) {\n        setTimeout(() => {\n          element.click();\n        }, 400);\n      } else if (typeof console !== \"undefined\") {\n        // eslint-disable-next-line no-console\n        console.warn(\"[AgentWidget] Element not found for selector:\", selector);\n      }\n    }\n    return {\n      handled: true,\n      displayText: asString(payload.text)\n    };\n  }\n};\n\nconst ensureArrayOfStrings = (value: unknown): string[] => {\n  if (Array.isArray(value)) {\n    return value.map((entry) => String(entry));\n  }\n  return [];\n};\n\nexport const createActionManager = (options: ActionManagerOptions) => {\n  let processedIds = new Set(\n    ensureArrayOfStrings(options.getSessionMetadata().processedActionMessageIds)\n  );\n\n  const syncFromMetadata = () => {\n    processedIds = new Set(\n      ensureArrayOfStrings(options.getSessionMetadata().processedActionMessageIds)\n    );\n  };\n\n  const persistProcessedIds = () => {\n    const latestIds = Array.from(processedIds);\n    options.updateSessionMetadata((prev) => ({\n      ...prev,\n      processedActionMessageIds: latestIds\n    }));\n  };\n\n  const process = (context: ActionManagerProcessContext): { text: string; persist: boolean; resubmit?: boolean } | null => {\n    if (\n      context.streaming ||\n      context.message.role !== \"assistant\" ||\n      !context.text ||\n      processedIds.has(context.message.id)\n    ) {\n      return null;\n    }\n\n    const parseSource =\n      (typeof context.raw === \"string\" && context.raw) ||\n      (typeof context.message.rawContent === \"string\" &&\n        context.message.rawContent) ||\n      (typeof context.text === \"string\" && context.text) ||\n      null;\n\n    if (\n      !parseSource &&\n      typeof context.text === \"string\" &&\n      context.text.trim().startsWith(\"{\") &&\n      typeof console !== \"undefined\"\n    ) {\n      // eslint-disable-next-line no-console\n      console.warn(\n        \"[AgentWidget] Structured response detected but no raw payload was provided. Ensure your stream parser returns { text, raw }.\"\n      );\n    }\n\n    const action = parseSource\n      ? options.parsers.reduce<AgentWidgetParsedAction | null>(\n          (acc, parser) =>\n            acc || parser?.({ text: parseSource, message: context.message }) || null,\n          null\n        )\n      : null;\n\n    if (!action) {\n      return null;\n    }\n\n    processedIds.add(context.message.id);\n    persistProcessedIds();\n\n    const eventPayload: AgentWidgetActionEventPayload = {\n      action,\n      message: context.message\n    };\n    options.emit(\"action:detected\", eventPayload);\n\n    for (const handler of options.handlers) {\n      if (!handler) continue;\n      try {\n        // Create triggerResubmit function that emits the resubmit event\n        // Handlers should call this AFTER async work completes (not return resubmit: true)\n        const triggerResubmit = () => {\n          options.emit(\"action:resubmit\", eventPayload);\n        };\n\n        const handlerResult = handler(action, {\n          message: context.message,\n          metadata: options.getSessionMetadata(),\n          updateMetadata: options.updateSessionMetadata,\n          document: options.documentRef,\n          triggerResubmit\n        } as AgentWidgetActionContext) as AgentWidgetActionHandlerResult | void;\n\n        if (!handlerResult) continue;\n\n        if (handlerResult.handled) {\n          // persistMessage defaults to true if not specified\n          const persist = handlerResult.persistMessage !== false;\n          const displayText = handlerResult.displayText !== undefined ? handlerResult.displayText : \"\";\n          // Return resubmit flag - the caller (ui.ts) will handle deferred resubmit\n          // after injectAssistantMessage is called (to avoid race conditions with async handlers)\n          return { text: displayText, persist, resubmit: handlerResult.resubmit };\n        }\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Action handler error:\", error);\n        }\n      }\n    }\n\n    return { text: \"\", persist: true };\n  };\n\n  return {\n    process,\n    syncFromMetadata\n  };\n};\n\n","import type {\n  AgentWidgetMessage,\n  AgentWidgetStorageAdapter,\n  AgentWidgetStoredState,\n  PersonaArtifactRecord\n} from \"../types\";\n\nconst safeJsonParse = (value: string | null) => {\n  if (!value) return null;\n  try {\n    return JSON.parse(value);\n  } catch (error) {\n    if (typeof console !== \"undefined\") {\n      // eslint-disable-next-line no-console\n      console.error(\"[AgentWidget] Failed to parse stored state:\", error);\n    }\n    return null;\n  }\n};\n\nconst sanitizeMessages = (messages: AgentWidgetMessage[]) =>\n  messages.map((message) => ({\n    ...message,\n    streaming: false\n  }));\n\nconst sanitizeArtifacts = (artifacts: PersonaArtifactRecord[]) =>\n  artifacts.map((artifact) => ({\n    ...artifact,\n    status: \"complete\" as const\n  }));\n\nexport const createLocalStorageAdapter = (\n  key = \"persona-state\"\n): AgentWidgetStorageAdapter => {\n  const getStorage = () => {\n    if (typeof window === \"undefined\" || !window.localStorage) {\n      return null;\n    }\n    return window.localStorage;\n  };\n\n  return {\n    load: () => {\n      const storage = getStorage();\n      if (!storage) return null;\n      return safeJsonParse(storage.getItem(key));\n    },\n    save: (state: AgentWidgetStoredState) => {\n      const storage = getStorage();\n      if (!storage) return;\n      try {\n        const payload: AgentWidgetStoredState = {\n          ...state,\n          messages: state.messages ? sanitizeMessages(state.messages) : undefined,\n          artifacts: state.artifacts ? sanitizeArtifacts(state.artifacts) : undefined\n        };\n        storage.setItem(key, JSON.stringify(payload));\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Failed to persist state:\", error);\n        }\n      }\n    },\n    clear: () => {\n      const storage = getStorage();\n      if (!storage) return;\n      try {\n        storage.removeItem(key);\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Failed to clear stored state:\", error);\n        }\n      }\n    }\n  };\n};\n\n","import { parse as parsePartialJson, STR, OBJ } from \"partial-json\";\n\n/**\n * Represents a component directive extracted from JSON\n */\nexport interface ComponentDirective {\n  component: string;\n  props: Record<string, unknown>;\n  raw: string;\n}\n\n/**\n * Checks if a parsed object is a component directive\n */\nfunction isComponentDirective(obj: unknown): obj is { component: string; props?: unknown } {\n  if (!obj || typeof obj !== \"object\") return false;\n  if (!(\"component\" in obj)) return false;\n  const component = (obj as { component: unknown }).component;\n  return typeof component === \"string\" && component.length > 0;\n}\n\n/**\n * Extracts component directive from parsed JSON object\n */\nfunction extractComponentDirective(\n  parsed: unknown,\n  rawJson: string\n): ComponentDirective | null {\n  if (!isComponentDirective(parsed)) {\n    return null;\n  }\n\n  const props = parsed.props && typeof parsed.props === \"object\" && parsed.props !== null\n    ? (parsed.props as Record<string, unknown>)\n    : {};\n\n  return {\n    component: parsed.component,\n    props,\n    raw: rawJson\n  };\n}\n\n/**\n * Creates a parser that extracts component directives from JSON streams\n * This parser looks for objects with a \"component\" field and extracts\n * the component name and props incrementally as they stream in.\n */\nexport function createComponentStreamParser() {\n  let extractedDirective: ComponentDirective | null = null;\n  let processedLength = 0;\n\n  return {\n    /**\n     * Get the currently extracted component directive\n     */\n    getExtractedDirective: (): ComponentDirective | null => {\n      return extractedDirective;\n    },\n\n    /**\n     * Process a chunk of JSON and extract component directive if present\n     */\n    processChunk: (accumulatedContent: string): ComponentDirective | null => {\n      // Validate that the accumulated content looks like JSON\n      const trimmed = accumulatedContent.trim();\n      if (!trimmed.startsWith(\"{\") && !trimmed.startsWith(\"[\")) {\n        return null;\n      }\n\n      // Skip if no new content\n      if (accumulatedContent.length <= processedLength) {\n        return extractedDirective;\n      }\n\n      try {\n        // Parse partial JSON - allow partial strings and objects during streaming\n        // STR | OBJ allows incomplete strings and objects during streaming\n        const parsed = parsePartialJson(accumulatedContent, STR | OBJ);\n\n        // Try to extract component directive\n        const directive = extractComponentDirective(parsed, accumulatedContent);\n        if (directive) {\n          extractedDirective = directive;\n        }\n      } catch (error) {\n        // If parsing fails completely, keep the last extracted directive\n        // This can happen with very malformed JSON during streaming\n      }\n\n      // Update processed length\n      processedLength = accumulatedContent.length;\n\n      return extractedDirective;\n    },\n\n    /**\n     * Reset the parser state\n     */\n    reset: () => {\n      extractedDirective = null;\n      processedLength = 0;\n    }\n  };\n}\n\n/**\n * Type guard to check if an object is a component directive\n */\nexport function isComponentDirectiveType(obj: unknown): obj is ComponentDirective {\n  return (\n    typeof obj === \"object\" &&\n    obj !== null &&\n    \"component\" in obj &&\n    typeof (obj as { component: unknown }).component === \"string\" &&\n    \"props\" in obj &&\n    typeof (obj as { props: unknown }).props === \"object\"\n  );\n}\n","import { AgentWidgetMessage, AgentWidgetConfig } from \"../types\";\nimport { componentRegistry, ComponentContext } from \"../components/registry\";\nimport { ComponentDirective, createComponentStreamParser } from \"./component-parser\";\nimport { createStandardBubble as _createStandardBubble, MessageTransform } from \"../components/message-bubble\";\n\n/**\n * Options for component middleware\n */\nexport interface ComponentMiddlewareOptions {\n  config: AgentWidgetConfig;\n  message: AgentWidgetMessage;\n  transform: MessageTransform;\n  onPropsUpdate?: (props: Record<string, unknown>) => void;\n}\n\n/**\n * Renders a component directive into an HTMLElement\n */\nexport function renderComponentDirective(\n  directive: ComponentDirective,\n  options: ComponentMiddlewareOptions\n): HTMLElement | null {\n  const { config, message, onPropsUpdate } = options;\n\n  // Get component renderer from registry\n  const renderer = componentRegistry.get(directive.component);\n  if (!renderer) {\n    // Component not found, fall back to default rendering\n    console.warn(\n      `[ComponentMiddleware] Component \"${directive.component}\" not found in registry. Falling back to default rendering.`\n    );\n    return null;\n  }\n\n  // Create component context\n  const context: ComponentContext = {\n    message,\n    config,\n    updateProps: (newProps: Record<string, unknown>) => {\n      if (onPropsUpdate) {\n        onPropsUpdate(newProps);\n      }\n    }\n  };\n\n  try {\n    // Render the component\n    const element = renderer(directive.props, context);\n    return element;\n  } catch (error) {\n    console.error(\n      `[ComponentMiddleware] Error rendering component \"${directive.component}\":`,\n      error\n    );\n    return null;\n  }\n}\n\n/**\n * Creates middleware that processes component directives from streamed JSON\n */\nexport function createComponentMiddleware() {\n  const parser = createComponentStreamParser();\n\n  return {\n    /**\n     * Process accumulated content and extract component directive\n     */\n    processChunk: (accumulatedContent: string): ComponentDirective | null => {\n      return parser.processChunk(accumulatedContent);\n    },\n\n    /**\n     * Get the currently extracted directive\n     */\n    getDirective: (): ComponentDirective | null => {\n      return parser.getExtractedDirective();\n    },\n\n    /**\n     * Reset the parser state\n     */\n    reset: () => {\n      parser.reset();\n    }\n  };\n}\n\n/**\n * Picks the field that may carry a JSON directive payload. Streamed messages\n * populate `rawContent`; manually injected messages may pass the JSON via\n * `content` directly. We try `rawContent` first, then fall back to `content`\n * when it looks like JSON, so both code paths render the same way.\n */\nfunction selectDirectiveSource(message: AgentWidgetMessage): string | null {\n  if (typeof message.rawContent === \"string\" && message.rawContent.length > 0) {\n    return message.rawContent;\n  }\n  if (typeof message.content === \"string\") {\n    const trimmed = message.content.trim();\n    if (trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\")) {\n      return message.content;\n    }\n  }\n  return null;\n}\n\n/**\n * Checks if a message contains a component directive.\n *\n * Looks at `rawContent` first (the field set by stream parsers); falls back\n * to `content` when it looks like JSON, so injected messages that pass the\n * directive via `content` (or have no `rawContent`) are still recognized.\n */\nexport function hasComponentDirective(message: AgentWidgetMessage): boolean {\n  const source = selectDirectiveSource(message);\n  if (!source) return false;\n\n  try {\n    const parsed = JSON.parse(source);\n    return (\n      typeof parsed === \"object\" &&\n      parsed !== null &&\n      \"component\" in parsed &&\n      typeof (parsed as { component: unknown }).component === \"string\"\n    );\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Extracts component directive from a complete message.\n *\n * Looks at `rawContent` first (the field set by stream parsers); falls back\n * to `content` when it looks like JSON, so injected messages that pass the\n * directive via `content` (or have no `rawContent`) render the same as\n * streamed ones.\n */\nexport function extractComponentDirectiveFromMessage(\n  message: AgentWidgetMessage\n): ComponentDirective | null {\n  const source = selectDirectiveSource(message);\n  if (!source) return null;\n\n  try {\n    const parsed = JSON.parse(source);\n    if (\n      typeof parsed === \"object\" &&\n      parsed !== null &&\n      \"component\" in parsed &&\n      typeof (parsed as { component: unknown }).component === \"string\"\n    ) {\n      const directive = parsed as { component: string; props?: unknown };\n      return {\n        component: directive.component,\n        props: (directive.props && typeof directive.props === \"object\" && directive.props !== null\n          ? directive.props\n          : {}) as Record<string, unknown>,\n        raw: source\n      };\n    }\n  } catch {\n    // Not valid JSON or not a component directive\n  }\n\n  return null;\n}\n","/**\n * Feedback UI components for CSAT and NPS collection\n */\n\nexport type CSATFeedbackOptions = {\n  /** Callback when user submits CSAT feedback */\n  onSubmit: (rating: number, comment?: string) => void | Promise<void>;\n  /** Callback when user dismisses the feedback form */\n  onDismiss?: () => void;\n  /** Title text */\n  title?: string;\n  /** Subtitle/question text */\n  subtitle?: string;\n  /** Placeholder for optional comment field */\n  commentPlaceholder?: string;\n  /** Submit button text */\n  submitText?: string;\n  /** Skip button text */\n  skipText?: string;\n  /** Show comment field */\n  showComment?: boolean;\n  /** Rating labels (5 items for ratings 1-5) */\n  ratingLabels?: [string, string, string, string, string];\n};\n\nexport type NPSFeedbackOptions = {\n  /** Callback when user submits NPS feedback */\n  onSubmit: (rating: number, comment?: string) => void | Promise<void>;\n  /** Callback when user dismisses the feedback form */\n  onDismiss?: () => void;\n  /** Title text */\n  title?: string;\n  /** Subtitle/question text */\n  subtitle?: string;\n  /** Placeholder for optional comment field */\n  commentPlaceholder?: string;\n  /** Submit button text */\n  submitText?: string;\n  /** Skip button text */\n  skipText?: string;\n  /** Show comment field */\n  showComment?: boolean;\n  /** Low label (left side) */\n  lowLabel?: string;\n  /** High label (right side) */\n  highLabel?: string;\n};\n\nconst defaultCSATLabels: [string, string, string, string, string] = [\n  'Very dissatisfied',\n  'Dissatisfied',\n  'Neutral',\n  'Satisfied',\n  'Very satisfied'\n];\n\n/**\n * Create a CSAT (Customer Satisfaction) feedback form\n * Rating scale: 1-5\n */\nexport function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {\n  const {\n    onSubmit,\n    onDismiss,\n    title = 'How satisfied are you?',\n    subtitle = 'Please rate your experience',\n    commentPlaceholder = 'Share your thoughts (optional)...',\n    submitText = 'Submit',\n    skipText = 'Skip',\n    showComment = true,\n    ratingLabels = defaultCSATLabels,\n  } = options;\n\n  const container = document.createElement('div');\n  container.className = 'persona-feedback-container persona-feedback-csat';\n  container.setAttribute('role', 'dialog');\n  container.setAttribute('aria-label', 'Customer satisfaction feedback');\n\n  let selectedRating: number | null = null;\n\n  // Create inner content\n  const content = document.createElement('div');\n  content.className = 'persona-feedback-content';\n\n  // Header\n  const header = document.createElement('div');\n  header.className = 'persona-feedback-header';\n  \n  const titleEl = document.createElement('h3');\n  titleEl.className = 'persona-feedback-title';\n  titleEl.textContent = title;\n  header.appendChild(titleEl);\n\n  const subtitleEl = document.createElement('p');\n  subtitleEl.className = 'persona-feedback-subtitle';\n  subtitleEl.textContent = subtitle;\n  header.appendChild(subtitleEl);\n\n  content.appendChild(header);\n\n  // Rating buttons (1-5 stars or numbers)\n  const ratingContainer = document.createElement('div');\n  ratingContainer.className = 'persona-feedback-rating persona-feedback-rating-csat';\n  ratingContainer.setAttribute('role', 'radiogroup');\n  ratingContainer.setAttribute('aria-label', 'Satisfaction rating from 1 to 5');\n\n  const ratingButtons: HTMLButtonElement[] = [];\n\n  for (let i = 1; i <= 5; i++) {\n    const ratingButton = document.createElement('button');\n    ratingButton.type = 'button';\n    ratingButton.className = 'persona-feedback-rating-btn persona-feedback-star-btn';\n    ratingButton.setAttribute('role', 'radio');\n    ratingButton.setAttribute('aria-checked', 'false');\n    ratingButton.setAttribute('aria-label', `${i} star${i > 1 ? 's' : ''}: ${ratingLabels[i - 1]}`);\n    ratingButton.title = ratingLabels[i - 1];\n    ratingButton.dataset.rating = String(i);\n\n    // Star icon (filled when selected)\n    ratingButton.innerHTML = `\n      <svg class=\"persona-feedback-star\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n        <polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"></polygon>\n      </svg>\n    `;\n\n    ratingButton.addEventListener('click', () => {\n      selectedRating = i;\n      ratingButtons.forEach((btn, index) => {\n        const isSelected = index < i;\n        btn.classList.toggle('selected', isSelected);\n        btn.setAttribute('aria-checked', index === i - 1 ? 'true' : 'false');\n      });\n    });\n\n    ratingButtons.push(ratingButton);\n    ratingContainer.appendChild(ratingButton);\n  }\n\n  content.appendChild(ratingContainer);\n\n  // Comment field\n  let commentTextarea: HTMLTextAreaElement | null = null;\n  if (showComment) {\n    const commentContainer = document.createElement('div');\n    commentContainer.className = 'persona-feedback-comment-container';\n    \n    commentTextarea = document.createElement('textarea');\n    commentTextarea.className = 'persona-feedback-comment';\n    commentTextarea.placeholder = commentPlaceholder;\n    commentTextarea.rows = 3;\n    commentTextarea.setAttribute('aria-label', 'Additional comments');\n    \n    commentContainer.appendChild(commentTextarea);\n    content.appendChild(commentContainer);\n  }\n\n  // Action buttons\n  const actions = document.createElement('div');\n  actions.className = 'persona-feedback-actions';\n\n  const skipButton = document.createElement('button');\n  skipButton.type = 'button';\n  skipButton.className = 'persona-feedback-btn persona-feedback-btn-skip';\n  skipButton.textContent = skipText;\n  skipButton.addEventListener('click', () => {\n    onDismiss?.();\n    container.remove();\n  });\n\n  const submitButton = document.createElement('button');\n  submitButton.type = 'button';\n  submitButton.className = 'persona-feedback-btn persona-feedback-btn-submit';\n  submitButton.textContent = submitText;\n  submitButton.addEventListener('click', async () => {\n    if (selectedRating === null) {\n      // Shake the rating container to indicate selection required\n      ratingContainer.classList.add('persona-feedback-shake');\n      setTimeout(() => ratingContainer.classList.remove('persona-feedback-shake'), 500);\n      return;\n    }\n    \n    submitButton.disabled = true;\n    submitButton.textContent = 'Submitting...';\n    \n    try {\n      const comment = commentTextarea?.value.trim() || undefined;\n      await onSubmit(selectedRating, comment);\n      container.remove();\n    } catch (error) {\n      submitButton.disabled = false;\n      submitButton.textContent = submitText;\n      // eslint-disable-next-line no-console\n      console.error('[CSAT Feedback] Failed to submit:', error);\n    }\n  });\n\n  actions.appendChild(skipButton);\n  actions.appendChild(submitButton);\n  content.appendChild(actions);\n\n  container.appendChild(content);\n\n  return container;\n}\n\n/**\n * Create an NPS (Net Promoter Score) feedback form\n * Rating scale: 0-10\n */\nexport function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {\n  const {\n    onSubmit,\n    onDismiss,\n    title = 'How likely are you to recommend us?',\n    subtitle = 'On a scale of 0 to 10',\n    commentPlaceholder = 'What could we do better? (optional)...',\n    submitText = 'Submit',\n    skipText = 'Skip',\n    showComment = true,\n    lowLabel = 'Not likely',\n    highLabel = 'Very likely',\n  } = options;\n\n  const container = document.createElement('div');\n  container.className = 'persona-feedback-container persona-feedback-nps';\n  container.setAttribute('role', 'dialog');\n  container.setAttribute('aria-label', 'Net Promoter Score feedback');\n\n  let selectedRating: number | null = null;\n\n  // Create inner content\n  const content = document.createElement('div');\n  content.className = 'persona-feedback-content';\n\n  // Header\n  const header = document.createElement('div');\n  header.className = 'persona-feedback-header';\n  \n  const titleEl = document.createElement('h3');\n  titleEl.className = 'persona-feedback-title';\n  titleEl.textContent = title;\n  header.appendChild(titleEl);\n\n  const subtitleEl = document.createElement('p');\n  subtitleEl.className = 'persona-feedback-subtitle';\n  subtitleEl.textContent = subtitle;\n  header.appendChild(subtitleEl);\n\n  content.appendChild(header);\n\n  // Rating buttons (0-10)\n  const ratingContainer = document.createElement('div');\n  ratingContainer.className = 'persona-feedback-rating persona-feedback-rating-nps';\n  ratingContainer.setAttribute('role', 'radiogroup');\n  ratingContainer.setAttribute('aria-label', 'Likelihood rating from 0 to 10');\n\n  // Labels row\n  const labelsRow = document.createElement('div');\n  labelsRow.className = 'persona-feedback-labels';\n  \n  const lowLabelEl = document.createElement('span');\n  lowLabelEl.className = 'persona-feedback-label-low';\n  lowLabelEl.textContent = lowLabel;\n  \n  const highLabelEl = document.createElement('span');\n  highLabelEl.className = 'persona-feedback-label-high';\n  highLabelEl.textContent = highLabel;\n  \n  labelsRow.appendChild(lowLabelEl);\n  labelsRow.appendChild(highLabelEl);\n  \n  // Numbers row\n  const numbersRow = document.createElement('div');\n  numbersRow.className = 'persona-feedback-numbers';\n\n  const ratingButtons: HTMLButtonElement[] = [];\n\n  for (let i = 0; i <= 10; i++) {\n    const ratingButton = document.createElement('button');\n    ratingButton.type = 'button';\n    ratingButton.className = 'persona-feedback-rating-btn persona-feedback-number-btn';\n    ratingButton.setAttribute('role', 'radio');\n    ratingButton.setAttribute('aria-checked', 'false');\n    ratingButton.setAttribute('aria-label', `Rating ${i} out of 10`);\n    ratingButton.textContent = String(i);\n    ratingButton.dataset.rating = String(i);\n\n    // Color coding: detractors (0-6), passives (7-8), promoters (9-10)\n    if (i <= 6) {\n      ratingButton.classList.add('persona-feedback-detractor');\n    } else if (i <= 8) {\n      ratingButton.classList.add('persona-feedback-passive');\n    } else {\n      ratingButton.classList.add('persona-feedback-promoter');\n    }\n\n    ratingButton.addEventListener('click', () => {\n      selectedRating = i;\n      ratingButtons.forEach((btn, index) => {\n        btn.classList.toggle('selected', index === i);\n        btn.setAttribute('aria-checked', index === i ? 'true' : 'false');\n      });\n    });\n\n    ratingButtons.push(ratingButton);\n    numbersRow.appendChild(ratingButton);\n  }\n\n  ratingContainer.appendChild(labelsRow);\n  ratingContainer.appendChild(numbersRow);\n  content.appendChild(ratingContainer);\n\n  // Comment field\n  let commentTextarea: HTMLTextAreaElement | null = null;\n  if (showComment) {\n    const commentContainer = document.createElement('div');\n    commentContainer.className = 'persona-feedback-comment-container';\n    \n    commentTextarea = document.createElement('textarea');\n    commentTextarea.className = 'persona-feedback-comment';\n    commentTextarea.placeholder = commentPlaceholder;\n    commentTextarea.rows = 3;\n    commentTextarea.setAttribute('aria-label', 'Additional comments');\n    \n    commentContainer.appendChild(commentTextarea);\n    content.appendChild(commentContainer);\n  }\n\n  // Action buttons\n  const actions = document.createElement('div');\n  actions.className = 'persona-feedback-actions';\n\n  const skipButton = document.createElement('button');\n  skipButton.type = 'button';\n  skipButton.className = 'persona-feedback-btn persona-feedback-btn-skip';\n  skipButton.textContent = skipText;\n  skipButton.addEventListener('click', () => {\n    onDismiss?.();\n    container.remove();\n  });\n\n  const submitButton = document.createElement('button');\n  submitButton.type = 'button';\n  submitButton.className = 'persona-feedback-btn persona-feedback-btn-submit';\n  submitButton.textContent = submitText;\n  submitButton.addEventListener('click', async () => {\n    if (selectedRating === null) {\n      // Shake the rating container to indicate selection required\n      numbersRow.classList.add('persona-feedback-shake');\n      setTimeout(() => numbersRow.classList.remove('persona-feedback-shake'), 500);\n      return;\n    }\n    \n    submitButton.disabled = true;\n    submitButton.textContent = 'Submitting...';\n    \n    try {\n      const comment = commentTextarea?.value.trim() || undefined;\n      await onSubmit(selectedRating, comment);\n      container.remove();\n    } catch (error) {\n      submitButton.disabled = false;\n      submitButton.textContent = submitText;\n      // eslint-disable-next-line no-console\n      console.error('[NPS Feedback] Failed to submit:', error);\n    }\n  });\n\n  actions.appendChild(skipButton);\n  actions.appendChild(submitButton);\n  content.appendChild(actions);\n\n  container.appendChild(content);\n\n  return container;\n}\n\n\n\n","import { escapeHtml, createMarkdownProcessorFromConfig } from \"./postprocessors\";\nimport { resolveSanitizer } from \"./utils/sanitize\";\nimport { stabilizeStreamingTables } from \"./utils/streaming-table\";\nimport { onMarkdownParsersReady, getMarkdownParsersSync } from \"./markdown-parsers-loader\";\nimport { AgentWidgetSession, AgentWidgetSessionStatus } from \"./session\";\nimport {\n  AgentWidgetConfig,\n  AgentWidgetApprovalDecisionOptions,\n  AgentWidgetMessage,\n  AgentWidgetEvent,\n  AgentWidgetStorageAdapter,\n  AgentWidgetStoredState,\n  AgentWidgetControllerEventMap,\n  AgentWidgetVoiceStateEvent,\n  AgentWidgetStateEvent,\n  AgentWidgetStateSnapshot,\n  WidgetLayoutSlot,\n  SlotRenderer,\n  AgentWidgetMessageFeedback,\n  ContentPart,\n  InjectMessageOptions,\n  InjectAssistantMessageOptions,\n  InjectUserMessageOptions,\n  InjectSystemMessageOptions,\n  InjectComponentDirectiveOptions,\n  LoadingIndicatorRenderContext,\n  IdleIndicatorRenderContext,\n  VoiceStatus,\n  ReadAloudState,\n  PersonaArtifactRecord,\n  PersonaArtifactManualUpsert,\n  PersonaArtifactFileMeta,\n  PersonaArtifactActionContext\n} from \"./types\";\nimport { AttachmentManager } from \"./utils/attachment-manager\";\nimport { createTextPart, ALL_SUPPORTED_MIME_TYPES } from \"./utils/content\";\nimport { applyThemeVariables, createThemeObserver, getActiveTheme } from \"./utils/theme\";\nimport { resolveTokenValue } from \"./utils/tokens\";\nimport { renderLucideIcon } from \"./utils/icons\";\nimport { createElement, createElementInDocument } from \"./utils/dom\";\nimport { downloadInfoFor } from \"./utils/artifact-file\";\nimport { artifactCopyText } from \"./components/artifact-preview\";\nimport { morphMessages } from \"./utils/morph\";\nimport { normalizeCopiedSelectionText } from \"./utils/copy-selection\";\nimport {\n  navigateComposerHistory,\n  INITIAL_HISTORY_STATE,\n  type ComposerHistoryState\n} from \"./utils/composer-history\";\nimport { computeMessageFingerprint, createMessageCache, getCachedWrapper, setCachedWrapper, pruneCache } from \"./utils/message-fingerprint\";\nimport {\n  computeAnchorScrollState,\n  computeShrunkSpacerHeight,\n  createFollowStateController,\n  getScrollBottomOffset,\n  hasSelectionWithin,\n  isElementNearBottom,\n  resolveFollowStateFromScroll,\n  resolveFollowStateFromWheel\n} from \"./utils/auto-follow\";\nimport { statusCopy, DEFAULT_OVERLAY_Z_INDEX, PORTALED_OVERLAY_Z_INDEX } from \"./utils/constants\";\nimport {\n  applyStreamBuffer,\n  createSkeletonPlaceholder,\n  createStreamCaret,\n  detachAllPlugins,\n  ensurePluginActive,\n  resolveStreamAnimation,\n  resolveStreamAnimationPlugin,\n  wrapStreamAnimation,\n} from \"./utils/stream-animation\";\nimport { syncOverlayHostStacking } from \"./utils/overlay-host-stacking\";\nimport { acquireScrollLock } from \"./utils/scroll-lock\";\nimport { isComposerBarMountMode, isDockedMountMode, resolveDockConfig } from \"./utils/dock\";\nimport { LauncherButton } from \"./components/launcher\";\nimport { buildHeader, buildComposer, attachHeaderToContainer } from \"./components/panel\";\nimport { createWidgetView, resolveLauncher } from \"./components/widget-view\";\nimport { HEADER_THEME_CSS } from \"./components/header-builder\";\nimport { buildHeaderWithLayout } from \"./components/header-layouts\";\nimport { positionMap } from \"./utils/positioning\";\nimport type { HeaderElements as _HeaderElements, ComposerElements as _ComposerElements } from \"./components/panel\";\nimport { MessageTransform, MessageActionCallbacks, LoadingIndicatorRenderer } from \"./components/message-bubble\";\nimport { createStandardBubble, createTypingIndicator } from \"./components/message-bubble\";\nimport { createReasoningBubble, reasoningExpansionState, updateReasoningBubbleUI } from \"./components/reasoning-bubble\";\nimport { createToolBubble, toolExpansionState, updateToolBubbleUI } from \"./components/tool-bubble\";\nimport {\n  buildStructuredAnswers,\n  ensureAskUserQuestionSheet,\n  getCurrentIndex,\n  getQuestionCount,\n  getSelectedLabels,\n  isAskUserQuestionMessage,\n  isGroupedSheet,\n  navigateToPage,\n  parseAskUserQuestionPayload,\n  readAnswersFromSheet,\n  removeAskUserQuestionSheet,\n  setCurrentAnswer,\n} from \"./components/ask-user-question-bubble\";\nimport {\n  isSuggestRepliesMessage,\n  latestAgentSuggestions,\n} from \"./suggest-replies-tool\";\nimport { formatElapsedMs } from \"./utils/formatting\";\nimport { approvalDetailsExpansionState, createApprovalBubble, updateApprovalDetailsUI } from \"./components/approval-bubble\";\nimport { createBuiltInApprovalPlugin } from \"./components/approval-actions\";\nimport { createSuggestions } from \"./components/suggestions\";\nimport { EventStreamBuffer } from \"./utils/event-stream-buffer\";\nimport { EventStreamStore } from \"./utils/event-stream-store\";\nimport { ThroughputTracker } from \"./utils/throughput-tracker\";\nimport { createEventStreamView } from \"./components/event-stream-view\";\nimport { createArtifactPane, type ArtifactPaneApi } from \"./components/artifact-pane\";\nimport {\n  hasLiveInlineArtifactBlock,\n  updateInlineArtifactBlocks\n} from \"./components/artifact-inline\";\nimport {\n  artifactsSidebarEnabled,\n  applyArtifactLayoutCssVars,\n  applyArtifactPaneAppearance,\n  isArtifactPaneAppearanceDetached,\n  shouldExpandLauncherForArtifacts\n} from \"./utils/artifact-gate\";\nimport { resolveArtifactDisplayMode } from \"./utils/artifact-display\";\nimport { readFlexGapPx, resolveArtifactPaneWidthPx } from \"./utils/artifact-resize\";\nimport { enhanceWithForms } from \"./components/forms\";\nimport { pluginRegistry } from \"./plugins/registry\";\nimport { mergeWithDefaults, DEFAULT_FLOATING_LAUNCHER_WIDTH } from \"./defaults\";\nimport { createEventBus } from \"./utils/events\";\nimport {\n  createActionManager,\n  defaultActionHandlers,\n  defaultJsonActionParser\n} from \"./utils/actions\";\nimport { createLocalStorageAdapter } from \"./utils/storage\";\nimport { componentRegistry } from \"./components/registry\";\nimport {\n  renderComponentDirective,\n  extractComponentDirectiveFromMessage,\n  hasComponentDirective\n} from \"./utils/component-middleware\";\nimport {\n  createCSATFeedback,\n  createNPSFeedback,\n  type CSATFeedbackOptions,\n  type NPSFeedbackOptions\n} from \"./components/feedback\";\n\n// Default localStorage key for chat history (automatically cleared on clear chat)\nconst DEFAULT_CHAT_HISTORY_STORAGE_KEY = \"persona-chat-history\";\nconst VOICE_STATE_RESTORE_WINDOW = 30 * 1000;\n// Split desktop boundary; must match widget.css artifact media queries (min-width:641px).\nconst ARTIFACT_SPLIT_DESKTOP_MIN = 641;\n\nconst IMAGE_FILE_EXTENSION_BY_MIME_TYPE: Record<string, string> = {\n  \"image/png\": \"png\",\n  \"image/jpeg\": \"jpg\",\n  \"image/jpg\": \"jpg\",\n  \"image/gif\": \"gif\",\n  \"image/webp\": \"webp\",\n  \"image/svg+xml\": \"svg\",\n  \"image/bmp\": \"bmp\",\n  \"image/tiff\": \"tiff\"\n};\n\nfunction getClipboardImageFiles(clipboardData: DataTransfer | null): File[] {\n  if (!clipboardData) return [];\n\n  const imageFiles: File[] = [];\n  const clipboardItems = Array.from(clipboardData.items ?? []);\n\n  for (const item of clipboardItems) {\n    if (item.kind !== \"file\" || !item.type.startsWith(\"image/\")) continue;\n    const file = item.getAsFile();\n    if (!file) continue;\n\n    if (file.name) {\n      imageFiles.push(file);\n      continue;\n    }\n\n    const extension = IMAGE_FILE_EXTENSION_BY_MIME_TYPE[file.type] ?? \"png\";\n    imageFiles.push(\n      new File(\n        [file],\n        `clipboard-image-${Date.now()}.${extension}`,\n        {\n          type: file.type,\n          lastModified: Date.now()\n        }\n      )\n    );\n  }\n\n  if (imageFiles.length > 0) {\n    return imageFiles;\n  }\n\n  for (const file of Array.from(clipboardData.files ?? [])) {\n    if (file.type.startsWith(\"image/\")) {\n      imageFiles.push(file);\n    }\n  }\n\n  return imageFiles;\n}\n\nfunction dataTransferHasFiles(\n  dataTransfer: DataTransfer | null\n): dataTransfer is DataTransfer {\n  if (!dataTransfer) return false;\n  const types = dataTransfer.types;\n  if (!types) return false;\n  // Real browsers return DOMStringList which has .contains(); test polyfills use plain arrays.\n  if (typeof (types as unknown as { contains?: unknown }).contains === \"function\") {\n    return (types as unknown as DOMStringList).contains(\"Files\");\n  }\n  return Array.from(types).includes(\"Files\");\n}\n\n// ============================================================================\n// PERSIST STATE HELPERS\n// ============================================================================\n\ntype NormalizedPersistConfig = {\n  storage: 'local' | 'session';\n  keyPrefix: string;\n  persist: {\n    openState: boolean;\n    voiceState: boolean;\n    focusInput: boolean;\n  };\n  clearOnChatClear: boolean;\n};\n\n/**\n * Normalize persistState config - handles both boolean and object forms\n */\nfunction normalizePersistStateConfig(\n  config: boolean | { storage?: 'local' | 'session'; keyPrefix?: string; persist?: { openState?: boolean; voiceState?: boolean; focusInput?: boolean }; clearOnChatClear?: boolean } | undefined\n): NormalizedPersistConfig | null {\n  if (!config) return null;\n  \n  if (config === true) {\n    // Use defaults\n    return {\n      storage: 'session',\n      keyPrefix: 'persona-',\n      persist: {\n        openState: true,\n        voiceState: true,\n        focusInput: true\n      },\n      clearOnChatClear: true\n    };\n  }\n  \n  // Object config - merge with defaults\n  return {\n    storage: config.storage ?? 'session',\n    keyPrefix: config.keyPrefix ?? 'persona-',\n    persist: {\n      openState: config.persist?.openState ?? true,\n      voiceState: config.persist?.voiceState ?? true,\n      focusInput: config.persist?.focusInput ?? true\n    },\n    clearOnChatClear: config.clearOnChatClear ?? true\n  };\n}\n\n/**\n * Get the storage object based on config\n */\nfunction getPersistStorage(storageType: 'local' | 'session'): Storage | null {\n  try {\n    const storage = storageType === 'local' ? localStorage : sessionStorage;\n    // Test that storage is actually available\n    const testKey = '__persist_test__';\n    storage.setItem(testKey, '1');\n    storage.removeItem(testKey);\n    return storage;\n  } catch {\n    return null;\n  }\n}\n\nconst ensureRecord = (value: unknown): Record<string, unknown> => {\n  if (!value || typeof value !== \"object\") {\n    return {};\n  }\n  return { ...(value as Record<string, unknown>) };\n};\n\nconst stripStreamingFromMessages = (messages: AgentWidgetMessage[]) =>\n  messages.map((message) => ({\n    ...message,\n    streaming: false\n  }));\n\ntype Controller = {\n  update: (config: AgentWidgetConfig) => void;\n  destroy: () => void;\n  open: () => void;\n  close: () => void;\n  toggle: () => void;\n  clearChat: () => void;\n  setMessage: (message: string) => boolean;\n  submitMessage: (message?: string) => boolean;\n  /**\n   * Manually retry a dropped durable stream (e.g. from a \"Reconnect\" button).\n   * No-op unless a resumable durable turn dropped and `reconnectStream` is set.\n   */\n  reconnect: () => void;\n  startVoiceRecognition: () => boolean;\n  stopVoiceRecognition: () => boolean;\n  /**\n   * Inject a message into the conversation with dual-content support.\n   * Auto-opens the widget if closed and launcher is enabled.\n   */\n  injectMessage: (options: InjectMessageOptions) => AgentWidgetMessage;\n  /**\n   * Convenience method for injecting assistant messages.\n   */\n  injectAssistantMessage: (options: InjectAssistantMessageOptions) => AgentWidgetMessage;\n  /**\n   * Convenience method for injecting user messages.\n   */\n  injectUserMessage: (options: InjectUserMessageOptions) => AgentWidgetMessage;\n  /**\n   * Convenience method for injecting system messages.\n   */\n  injectSystemMessage: (options: InjectSystemMessageOptions) => AgentWidgetMessage;\n  /**\n   * Inject multiple messages in a single batch with one sort and one render pass.\n   */\n  injectMessageBatch: (optionsList: InjectMessageOptions[]) => AgentWidgetMessage[];\n  /**\n   * Convenience method for injecting an assistant message that renders as a\n   * registered component: same shape Persona produces from a streamed\n   * `{ \"text\": \"...\", \"component\": \"...\", \"props\": {...} }` payload.\n   */\n  injectComponentDirective: (\n    options: InjectComponentDirectiveOptions\n  ) => AgentWidgetMessage;\n  /**\n   * @deprecated Use injectMessage() instead.\n   */\n  injectTestMessage: (event: AgentWidgetEvent) => void;\n  getMessages: () => AgentWidgetMessage[];\n  getStatus: () => AgentWidgetSessionStatus;\n  getPersistentMetadata: () => Record<string, unknown>;\n  updatePersistentMetadata: (\n    updater: (prev: Record<string, unknown>) => Record<string, unknown>\n  ) => void;\n  on: <K extends keyof AgentWidgetControllerEventMap>(\n    event: K,\n    handler: (payload: AgentWidgetControllerEventMap[K]) => void\n  ) => () => void;\n  off: <K extends keyof AgentWidgetControllerEventMap>(\n    event: K,\n    handler: (payload: AgentWidgetControllerEventMap[K]) => void\n  ) => void;\n  // State query methods\n  isOpen: () => boolean;\n  isVoiceActive: () => boolean;\n  // Read-aloud (text-to-speech) methods\n  toggleReadAloud: (messageId: string) => void;\n  stopReadAloud: () => void;\n  getReadAloudState: (messageId: string) => ReadAloudState;\n  onReadAloudChange: (\n    listener: (activeId: string | null, state: ReadAloudState) => void\n  ) => () => void;\n  getState: () => AgentWidgetStateSnapshot;\n  // Feedback methods (CSAT/NPS)\n  showCSATFeedback: (options?: Partial<CSATFeedbackOptions>) => void;\n  showNPSFeedback: (options?: Partial<NPSFeedbackOptions>) => void;\n  submitCSATFeedback: (rating: number, comment?: string) => Promise<void>;\n  submitNPSFeedback: (rating: number, comment?: string) => Promise<void>;\n  /**\n   * Connect an external SSE stream and process it through the SDK's\n   * native event pipeline (tools, reasoning, streaming text, etc.).\n   */\n  connectStream: (\n    stream: ReadableStream<Uint8Array>,\n    options?: { assistantMessageId?: string }\n  ) => Promise<void>;\n  /** Push a raw event into the event stream buffer (for testing/debugging) */\n  __pushEventStreamEvent: (event: { type: string; payload: unknown }) => void;\n  /** Opens the event stream panel */\n  showEventStream: () => void;\n  /** Closes the event stream panel */\n  hideEventStream: () => void;\n  /** Returns current visibility state of the event stream panel */\n  isEventStreamVisible: () => boolean;\n  /** Show artifact sidebar (no-op if features.artifacts.enabled is false) */\n  showArtifacts: () => void;\n  /** Hide artifact sidebar */\n  hideArtifacts: () => void;\n  /** Upsert an artifact programmatically */\n  upsertArtifact: (manual: PersonaArtifactManualUpsert) => PersonaArtifactRecord | null;\n  selectArtifact: (id: string) => void;\n  clearArtifacts: () => void;\n  /** Read current artifacts (useful on init to rebuild host-side tab state after hydration). */\n  getArtifacts: () => PersonaArtifactRecord[];\n  /** Read the currently selected artifact id (paired with `getArtifacts`). */\n  getSelectedArtifactId: () => string | null;\n  /**\n   * Focus the chat input. Returns true if focus succeeded, false if panel is closed\n   * (launcher mode) or textarea is unavailable.\n   */\n  focusInput: () => boolean;\n  /**\n   * Programmatically resolve a pending approval.\n   * @param approvalId - The approval ID to resolve\n   * @param decision - \"approved\" or \"denied\"\n   * @param options - Optional decision context (e.g. `{ remember: true }`),\n   *   forwarded to `config.approval.onDecision`.\n   */\n  resolveApproval: (\n    approvalId: string,\n    decision: 'approved' | 'denied',\n    options?: AgentWidgetApprovalDecisionOptions\n  ) => Promise<void>;\n};\n\nexport const buildPostprocessor = (\n  cfg: AgentWidgetConfig | undefined,\n  actionManager?: ReturnType<typeof createActionManager>,\n  onResubmitRequested?: () => void\n): MessageTransform => {\n  // Create markdown processor from config if markdown config is provided\n  // This allows users to enable markdown rendering via config.markdown\n  const markdownProcessor = cfg?.markdown\n    ? createMarkdownProcessorFromConfig(cfg.markdown)\n    : null;\n\n  // Resolve sanitizer: enabled by default, can be disabled or replaced\n  const sanitize = resolveSanitizer(cfg?.sanitize);\n\n  // Warn developers when a custom postprocessor is used with the default sanitizer,\n  // since DOMPurify will strip any tags/attributes not in the allowlist.\n  if (cfg?.postprocessMessage && sanitize && cfg?.sanitize === undefined) {\n    console.warn(\n      \"[Persona] A custom postprocessMessage is active with the default HTML sanitizer. \" +\n      \"Tags or attributes not in the built-in allowlist will be stripped. \" +\n      \"To keep custom HTML, set `sanitize: false` or provide a custom sanitize function.\"\n    );\n  }\n\n  return (context) => {\n    let nextText = context.text ?? \"\";\n    const rawPayload = context.message.rawContent ?? null;\n\n    if (actionManager) {\n      const actionResult = actionManager.process({\n        text: nextText,\n        raw: rawPayload ?? nextText,\n        message: context.message,\n        streaming: context.streaming\n      });\n      if (actionResult !== null) {\n        nextText = actionResult.text;\n        // Mark message as non-persistable if persist is false\n        if (!actionResult.persist) {\n          (context.message as any).__skipPersist = true;\n        }\n        // Request deferred resubmit if handler requested it (and message is complete)\n        // The actual resubmit will be triggered when injectAssistantMessage is called\n        if (actionResult.resubmit && !context.streaming && onResubmitRequested) {\n          onResubmitRequested();\n        }\n      }\n    }\n\n    // Priority: postprocessMessage > markdown config > escapeHtml.\n    //\n    // Degraded path (IIFE/CDN build before `markdown-parsers.js` resolves, or if\n    // it never loads): the markdown processor and the sanitizer BOTH fall back to\n    // escapeHtml, so the old `sanitize(markdownProcessor(text))` escaped twice and\n    // displayed entities literally (I'll -> &amp;#39;). Only run the sanitizer when\n    // it can actually parse HTML; escapeHtml output is already inert. Checked per\n    // render (not once at setup): the chunk lands later and the self-heal re-renders.\n    const parsersReady = getMarkdownParsersSync() !== null;\n    let html: string;\n    if (cfg?.postprocessMessage) {\n      const out = cfg.postprocessMessage({\n        ...context,\n        text: nextText,\n        raw: rawPayload ?? context.text ?? \"\"\n      });\n      // Custom HTML is NOT pre-escaped, so this stays a single pass even via the\n      // sanitizer's degraded fallback. Honors `sanitize: false` (pass-through) as before.\n      html = sanitize ? sanitize(out) : out;\n    } else if (markdownProcessor) {\n      // While streaming, normalize tables-in-progress so they render as a real\n      // <table> from the first row with a stable column count (Telegram-style\n      // space reservation). The final, non-streaming render is left untouched.\n      const source = context.streaming ? stabilizeStreamingTables(nextText) : nextText;\n      // Already escapeHtml(text) (single, safe) while parsers are not loaded.\n      const out = markdownProcessor(source);\n      html = sanitize && parsersReady ? sanitize(out) : out;\n    } else {\n      // Plain text: escapeHtml output is inert — never re-sanitize (the second escape).\n      html = escapeHtml(nextText);\n    }\n\n    return html;\n  };\n};\n\nfunction buildDropOverlay(\n  dropCfg?: NonNullable<AgentWidgetConfig[\"attachments\"]>[\"dropOverlay\"]\n): HTMLElement {\n  const overlay = createElement(\"div\", \"persona-attachment-drop-overlay\");\n  if (dropCfg?.background) overlay.style.setProperty(\"--persona-drop-overlay-bg\", dropCfg.background);\n  if (dropCfg?.backdropBlur !== undefined) overlay.style.setProperty(\"--persona-drop-overlay-blur\", dropCfg.backdropBlur);\n  if (dropCfg?.border) overlay.style.setProperty(\"--persona-drop-overlay-border\", dropCfg.border);\n  if (dropCfg?.borderRadius) overlay.style.setProperty(\"--persona-drop-overlay-radius\", dropCfg.borderRadius);\n  if (dropCfg?.inset) overlay.style.setProperty(\"--persona-drop-overlay-inset\", dropCfg.inset);\n  if (dropCfg?.labelSize) overlay.style.setProperty(\"--persona-drop-overlay-label-size\", dropCfg.labelSize);\n  if (dropCfg?.labelColor) overlay.style.setProperty(\"--persona-drop-overlay-label-color\", dropCfg.labelColor);\n\n  const iconName = dropCfg?.iconName ?? \"upload\";\n  const iconSize = dropCfg?.iconSize ?? \"48px\";\n  const iconColor = dropCfg?.iconColor ?? \"rgba(59, 130, 246, 0.6)\";\n  const iconStrokeWidth = dropCfg?.iconStrokeWidth ?? 0.5;\n  const iconSvg = renderLucideIcon(iconName, iconSize, iconColor, iconStrokeWidth);\n  if (iconSvg) overlay.appendChild(iconSvg);\n\n  if (dropCfg?.label) {\n    const labelEl = createElement(\"span\", \"persona-drop-overlay-label\");\n    labelEl.textContent = dropCfg.label;\n    overlay.appendChild(labelEl);\n  }\n  return overlay;\n}\n\nexport const createAgentExperience = (\n  mount: HTMLElement,\n  initialConfig?: AgentWidgetConfig,\n  runtimeOptions?: { debugTools?: boolean }\n): Controller => {\n  if (mount == null) {\n    throw new Error(\n      \"createAgentExperience: mount must be a non-null HTMLElement (e.g. pass document.getElementById(\\\"my-root\\\") after the node exists).\"\n    );\n  }\n  // Preserve original mount id as data attribute for window event instance scoping\n  if (mount.id && !mount.getAttribute(\"data-persona-instance\")) {\n    mount.setAttribute(\"data-persona-instance\", mount.id);\n  }\n  // Ensure root marker is present for Tailwind scoping and DOM traversal\n  if (!mount.hasAttribute(\"data-persona-root\")) {\n    mount.setAttribute(\"data-persona-root\", \"true\");\n  }\n\n  let config = mergeWithDefaults(initialConfig) as AgentWidgetConfig;\n  // Note: applyThemeVariables is called after applyFullHeightStyles() below\n  // because applyFullHeightStyles resets mount.style.cssText\n\n  // Get plugins for this instance\n  const plugins = pluginRegistry.getForInstance(config.plugins);\n\n  // The built-in approval renderer, shaped as a plugin. Resolved as a FALLBACK\n  // (not pushed into `plugins`) so a user `renderApproval` plugin always wins\n  // and a later config-update plugin push can't reorder ahead of it.\n  const { plugin: builtInApprovalPlugin, teardown: teardownBuiltInApprovals } =\n    createBuiltInApprovalPlugin();\n\n  // Register components from config\n  if (config.components) {\n    componentRegistry.registerAll(config.components);\n  }\n  const eventBus = createEventBus<AgentWidgetControllerEventMap>();\n\n  // When persistState is explicitly false, message-history persistence is\n  // disabled: including any user-supplied storageAdapter. This is the strict\n  // kill-switch semantic; pass `persistState: true` (or omit it) to opt in.\n  const messagePersistenceDisabled = config.persistState === false;\n  const storageAdapter: AgentWidgetStorageAdapter | null =\n    messagePersistenceDisabled\n      ? null\n      : (config.storageAdapter ?? createLocalStorageAdapter());\n  let persistentMetadata: Record<string, unknown> = {};\n  let pendingStoredState: Promise<AgentWidgetStoredState | null> | null = null;\n\n  let shouldOpenAfterStateLoaded = false;\n\n  // Helper to apply onStateLoaded hook and extract state.\n  // Supports both the legacy plain-state return and the new { state, open? } return.\n  const applyStateLoadedHook = (state: AgentWidgetStoredState): AgentWidgetStoredState => {\n    if (config.onStateLoaded) {\n      try {\n        const result = config.onStateLoaded(state);\n        if (result && typeof result === 'object' && 'state' in result) {\n          const { state: processedState, open } = result as { state: AgentWidgetStoredState; open?: boolean };\n          if (open) shouldOpenAfterStateLoaded = true;\n          return processedState;\n        }\n        return result as AgentWidgetStoredState;\n      } catch (error) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] onStateLoaded hook failed:\", error);\n        }\n      }\n    }\n    return state;\n  };\n\n  if (storageAdapter?.load) {\n    try {\n      const storedState = storageAdapter.load();\n      if (storedState && typeof (storedState as Promise<any>).then === \"function\") {\n        // For async storage, apply hook when promise resolves\n        pendingStoredState = (storedState as Promise<AgentWidgetStoredState | null>).then(\n          (resolved) => {\n            const state = resolved ?? { messages: [], metadata: {} };\n            return applyStateLoadedHook(state);\n          }\n        );\n      } else {\n        // Apply hook to synchronously loaded state (or empty state if nothing stored)\n        const baseState = (storedState as AgentWidgetStoredState) ?? { messages: [], metadata: {} };\n        const processedState = applyStateLoadedHook(baseState);\n        if (processedState.metadata) {\n          persistentMetadata = ensureRecord(processedState.metadata);\n        }\n        if (processedState.messages?.length) {\n          config = { ...config, initialMessages: processedState.messages };\n        }\n        if (processedState.artifacts?.length) {\n          config = {\n            ...config,\n            initialArtifacts: processedState.artifacts,\n            initialSelectedArtifactId: processedState.selectedArtifactId ?? null\n          };\n        }\n      }\n    } catch (error) {\n      if (typeof console !== \"undefined\") {\n        // eslint-disable-next-line no-console\n        console.error(\"[AgentWidget] Failed to load stored state:\", error);\n      }\n    }\n  } else if (config.onStateLoaded) {\n    // No storage adapter but hook exists - call with empty state\n    try {\n      const processedState = applyStateLoadedHook({ messages: [], metadata: {} });\n      if (processedState.messages?.length) {\n        config = { ...config, initialMessages: processedState.messages };\n      }\n    } catch (error) {\n      if (typeof console !== \"undefined\") {\n        // eslint-disable-next-line no-console\n        console.error(\"[AgentWidget] onStateLoaded hook failed:\", error);\n      }\n    }\n  }\n\n  const getSessionMetadata = () => persistentMetadata;\n  const updateSessionMetadata = (\n    updater: (prev: Record<string, unknown>) => Record<string, unknown>\n  ) => {\n    const next = updater({ ...persistentMetadata }) ?? {};\n    persistentMetadata = next;\n    persistState();\n  };\n\n  const resolvedActionParsers =\n    config.actionParsers && config.actionParsers.length\n      ? config.actionParsers\n      : [defaultJsonActionParser];\n\n  const resolvedActionHandlers =\n    config.actionHandlers && config.actionHandlers.length\n      ? config.actionHandlers\n      : [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];\n\n  let actionManager = createActionManager({\n    parsers: resolvedActionParsers,\n    handlers: resolvedActionHandlers,\n    getSessionMetadata,\n    updateSessionMetadata,\n    emit: eventBus.emit,\n    documentRef: typeof document !== \"undefined\" ? document : null\n  });\n  actionManager.syncFromMetadata();\n\n  let launcherEnabled = config.launcher?.enabled ?? true;\n  let autoExpand = config.launcher?.autoExpand ?? false;\n  const autoFocusInput = config.autoFocusInput ?? false;\n  let prevAutoExpand = autoExpand;\n  let prevLauncherEnabled = launcherEnabled;\n  let prevHeaderLayout = config.layout?.header?.layout;\n  let wasMobileFullscreen = false;\n  // Composer-bar mode behaves like a launcher-enabled panel for state/toggle\n  // purposes (open/close maps to expand/collapse) but does not render a\n  // launcher button. `isPanelToggleable()` covers both modes; checks that\n  // gate the launcher button itself stay on the raw `launcherEnabled` flag.\n  const isComposerBar = () => isComposerBarMountMode(config);\n  const isPanelToggleable = () => launcherEnabled || isComposerBar();\n  // Composer-bar starts collapsed (open=false). Inline embed (no launcher)\n  // is always open. Launcher mode honors `autoExpand`.\n  let open = isComposerBar() ? false : (launcherEnabled ? autoExpand : true);\n\n  // Track pending resubmit state for injection-triggered resubmit\n  // When a handler returns resubmit: true, we wait for injectAssistantMessage()\n  // to be called before triggering the actual resubmit (to avoid race conditions)\n  let pendingResubmit = false;\n  let pendingResubmitTimeout: ReturnType<typeof setTimeout> | null = null;\n\n  const handleResubmitRequested = () => {\n    pendingResubmit = true;\n    // Clear any existing timeout\n    if (pendingResubmitTimeout) {\n      clearTimeout(pendingResubmitTimeout);\n    }\n    // Safety timeout - clear flag after 10s if no injection occurs\n    pendingResubmitTimeout = setTimeout(() => {\n      if (pendingResubmit) {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.warn(\"[AgentWidget] Resubmit requested but no injection occurred within 10s\");\n        }\n        pendingResubmit = false;\n      }\n    }, 10000);\n  };\n\n  let postprocess = buildPostprocessor(config, actionManager, handleResubmitRequested);\n  let showReasoning = config.features?.showReasoning ?? true;\n  let showToolCalls = config.features?.showToolCalls ?? true;\n  let showEventStreamToggle = config.features?.showEventStreamToggle ?? false;\n  let scrollToBottomFeature = config.features?.scrollToBottom ?? {};\n  let scrollBehaviorFeature = config.features?.scrollBehavior ?? {};\n  const persistKeyPrefix = (typeof config.persistState === 'object' ? config.persistState?.keyPrefix : undefined) ?? \"persona-\";\n  const eventStreamDbName = `${persistKeyPrefix}event-stream`;\n  let eventStreamStore = showEventStreamToggle ? new EventStreamStore(eventStreamDbName) : null;\n  const eventStreamMaxEvents = config.features?.eventStream?.maxEvents ?? 2000;\n  let eventStreamBuffer = showEventStreamToggle ? new EventStreamBuffer(eventStreamMaxEvents, eventStreamStore) : null;\n  // Passive output-throughput tracker, fed from the same SSE tap as the buffer.\n  let throughputTracker = showEventStreamToggle ? new ThroughputTracker() : null;\n  let eventStreamView: ReturnType<typeof createEventStreamView> | null = null;\n  let eventStreamVisible = false;\n  let eventStreamRAF: number | null = null;\n  let eventStreamLastUpdate = 0;\n\n  // Open IndexedDB store and restore persisted events into the buffer\n  eventStreamStore?.open().then(() => {\n    return eventStreamBuffer?.restore();\n  }).catch(err => {\n    if (config.debug) console.warn('[AgentWidget] IndexedDB not available for event stream:', err);\n  });\n\n  // Create message action callbacks that emit events and optionally send to API\n  const messageActionCallbacks: MessageActionCallbacks = {\n    onCopy: (message: AgentWidgetMessage) => {\n      eventBus.emit(\"message:copy\", message);\n      // Send copy feedback to API if in client token mode\n      if (session?.isClientTokenMode()) {\n        session.submitMessageFeedback(message.id, 'copy').catch((error) => {\n          if (config.debug) {\n            // eslint-disable-next-line no-console\n            console.error(\"[AgentWidget] Failed to submit copy feedback:\", error);\n          }\n        });\n      }\n      // Call user-provided callback\n      config.messageActions?.onCopy?.(message);\n    },\n    onFeedback: (feedback: AgentWidgetMessageFeedback) => {\n      eventBus.emit(\"message:feedback\", feedback);\n      // Send feedback to API if in client token mode\n      if (session?.isClientTokenMode()) {\n        session.submitMessageFeedback(feedback.messageId, feedback.type).catch((error) => {\n          if (config.debug) {\n            // eslint-disable-next-line no-console\n            console.error(\"[AgentWidget] Failed to submit feedback:\", error);\n          }\n        });\n      }\n      // Call user-provided callback\n      config.messageActions?.onFeedback?.(feedback);\n    }\n  };\n  \n  // Get status indicator config\n  const statusConfig = config.statusIndicator ?? {};\n  const _getStatusText = (status: AgentWidgetSessionStatus): string => {\n    if (status === \"idle\") return statusConfig.idleText ?? statusCopy.idle;\n    if (status === \"connecting\") return statusConfig.connectingText ?? statusCopy.connecting;\n    if (status === \"connected\") return statusConfig.connectedText ?? statusCopy.connected;\n    if (status === \"error\") return statusConfig.errorText ?? statusCopy.error;\n    if (status === \"paused\") return statusConfig.pausedText ?? statusCopy.paused;\n    if (status === \"resuming\") return statusConfig.resumingText ?? statusCopy.resuming;\n    return statusCopy[status];\n  };\n\n  /** Update statusText element, rendering a link for idle status when idleLink is configured. */\n  function applyStatusToElement(el: HTMLElement, text: string, statusCfg: typeof statusConfig, status: string): void {\n    if (status === \"idle\" && statusCfg.idleLink) {\n      el.textContent = \"\";\n      const link = document.createElement(\"a\");\n      link.href = statusCfg.idleLink;\n      link.target = \"_blank\";\n      link.rel = \"noopener noreferrer\";\n      link.textContent = text;\n      link.style.color = \"inherit\";\n      link.style.textDecoration = \"none\";\n      el.appendChild(link);\n    } else {\n      el.textContent = text;\n    }\n  }\n\n  // The view layer (`components/widget-view.ts`) owns the one-time structural\n  // assembly (shell + panel) and groups the resulting refs into named regions.\n  // Behavior stays here in ui.ts; the locals below mirror the grouped refs so\n  // the surrounding orchestration code keeps its existing variable names.\n  const view = createWidgetView({ config, showClose: isPanelToggleable() });\n  const { wrapper, panel, pillRoot } = view.shell;\n  const panelElements = view.panelElements;\n  let {\n    container,\n    body,\n    messagesWrapper,\n    suggestions,\n    textarea,\n    sendButton,\n    sendButtonWrapper,\n    composerForm,\n    statusText,\n    introTitle,\n    introSubtitle,\n    closeButton,\n    iconHolder,\n    headerTitle,\n    headerSubtitle,\n    header,\n    footer,\n    actionsRow: _actionsRow,\n    leftActions,\n    rightActions\n  } = panelElements;\n  let setSendButtonMode = panelElements.setSendButtonMode;\n\n  // Use mutable references for mic button so we can update them dynamically\n  let micButton: HTMLButtonElement | null = panelElements.micButton;\n  let micButtonWrapper: HTMLElement | null = panelElements.micButtonWrapper;\n\n  // Use mutable references for attachment elements so we can create them dynamically\n  let attachmentButton: HTMLButtonElement | null = panelElements.attachmentButton;\n  let attachmentButtonWrapper: HTMLElement | null = panelElements.attachmentButtonWrapper;\n  let attachmentInput: HTMLInputElement | null = panelElements.attachmentInput;\n  let attachmentPreviewsContainer: HTMLElement | null = panelElements.attachmentPreviewsContainer;\n  container.classList.add(\"persona-relative\");\n  body.classList.add(\"persona-relative\");\n  const SCROLL_TO_BOTTOM_EDGE_OFFSET = 12;\n\n  const getScrollToBottomLabel = () => scrollToBottomFeature.label ?? \"\";\n  const getScrollToBottomIconName = () => scrollToBottomFeature.iconName ?? \"arrow-down\";\n  const isScrollToBottomEnabled = () => scrollToBottomFeature.enabled !== false;\n  // Default is \"anchor-top\" (see DEFAULT_WIDGET_CONFIG). This `??` only applies\n  // when a partial config sets `scrollBehavior.mode` to undefined explicitly;\n  // it must agree with the declared default.\n  const getScrollMode = () => scrollBehaviorFeature.mode ?? \"anchor-top\";\n  // \"Effectively following the bottom\" for streaming auto-scroll: true in\n  // follow mode, and in anchor-top when the current turn has no anchor (the\n  // no-anchor fallback). Drives `scheduleAutoScroll`, `handleContentResize`,\n  // and `isAwayFromLatest` so a no-anchor anchor-top turn behaves like follow.\n  const isFollowEffective = () =>\n    getScrollMode() === \"follow\" ||\n    (getScrollMode() === \"anchor-top\" && followFallbackActive);\n  const getAnchorTopOffset = () => scrollBehaviorFeature.anchorTopOffset ?? 16;\n  const getScrollRestorePosition = () =>\n    scrollBehaviorFeature.restorePosition ?? \"bottom\";\n  const isPauseOnInteractionEnabled = () =>\n    scrollBehaviorFeature.pauseOnInteraction === true;\n  // Defaults on alongside the anchor-top default so the pinned-turn UX keeps the\n  // unread count + \"streaming below\" hint; opt out with `false`.\n  const isActivityWhilePinnedEnabled = () =>\n    scrollBehaviorFeature.showActivityWhilePinned !== false;\n  const isAnnounceEnabled = () => scrollBehaviorFeature.announce === true;\n  const scrollToBottomButton = createElement(\n    \"button\",\n    \"persona-scroll-to-bottom-indicator persona-absolute persona-bottom-3 persona-left-1/2 persona-z-10 persona-flex persona-items-center persona-gap-1 persona-text-xs persona-transform persona--translate-x-1/2 persona-cursor-pointer\"\n  ) as HTMLButtonElement;\n  scrollToBottomButton.type = \"button\";\n  scrollToBottomButton.style.display = \"none\";\n  scrollToBottomButton.setAttribute(\"data-persona-scroll-to-bottom\", \"true\");\n  const scrollToBottomIcon = createElement(\"span\", \"persona-flex persona-items-center\");\n  const scrollToBottomLabel = createElement(\"span\", \"\");\n  // Count of messages that arrived while auto-follow was paused (or, in\n  // non-follow scroll modes, while the user was away from the bottom).\n  // Rendered as a small badge on the scroll-to-bottom affordance, mirroring\n  // the event stream view's \"Jump to latest (N)\" indicator.\n  const scrollToBottomCount = createElement(\"span\", \"\");\n  scrollToBottomCount.setAttribute(\"data-persona-scroll-to-bottom-count\", \"\");\n  scrollToBottomCount.style.display = \"none\";\n  scrollToBottomButton.append(scrollToBottomIcon, scrollToBottomLabel, scrollToBottomCount);\n  container.appendChild(scrollToBottomButton);\n\n  // Anchor-top scroll mode: zero-height spacer kept after the messages\n  // wrapper. Sized on send so the just-sent user message can be scrolled to\n  // the top of the viewport before the streamed response is tall enough to\n  // make that position reachable; shrinks as real content fills the space.\n  const anchorSpacer = createElement(\"div\", \"persona-stream-anchor-spacer\");\n  anchorSpacer.setAttribute(\"aria-hidden\", \"true\");\n  anchorSpacer.setAttribute(\"data-persona-anchor-spacer\", \"\");\n  anchorSpacer.style.flexShrink = \"0\";\n  anchorSpacer.style.pointerEvents = \"none\";\n  anchorSpacer.style.height = \"0px\";\n  body.appendChild(anchorSpacer);\n\n  // Visually-hidden polite live region for screen-reader announcements\n  // (Principle 15: announce important events at a comfortable pace, never\n  // token-by-token). Created unconditionally but only written to when\n  // `features.scrollBehavior.announce` is opted in.\n  const liveRegion = createElement(\"div\", \"persona-sr-only\");\n  liveRegion.setAttribute(\"aria-live\", \"polite\");\n  liveRegion.setAttribute(\"aria-atomic\", \"true\");\n  liveRegion.setAttribute(\"role\", \"status\");\n  liveRegion.setAttribute(\"data-persona-live-region\", \"\");\n  Object.assign(liveRegion.style, {\n    position: \"absolute\",\n    width: \"1px\",\n    height: \"1px\",\n    margin: \"-1px\",\n    padding: \"0\",\n    overflow: \"hidden\",\n    clip: \"rect(0 0 0 0)\",\n    clipPath: \"inset(50%)\",\n    whiteSpace: \"nowrap\",\n    border: \"0\",\n  } satisfies Partial<CSSStyleDeclaration>);\n  container.appendChild(liveRegion);\n  // Debounce announcements so a fast event sequence (e.g. several messages\n  // landing while away) collapses into one calm spoken update.\n  let announceTimer: ReturnType<typeof setTimeout> | null = null;\n  let pendingAnnouncement: string | null = null;\n  const announce = (message: string) => {\n    if (!isAnnounceEnabled() || !message) return;\n    pendingAnnouncement = message;\n    if (announceTimer !== null) return;\n    announceTimer = setTimeout(() => {\n      announceTimer = null;\n      // Re-check: `update()` may have disabled `announce` within the debounce\n      // window, and a stale message must not slip through to the live region.\n      if (pendingAnnouncement && isAnnounceEnabled()) {\n        liveRegion.textContent = pendingAnnouncement;\n      }\n      pendingAnnouncement = null;\n    }, 400);\n  };\n\n  const updateScrollToBottomButtonOffset = () => {\n    const footerHidden = footer.style.display === \"none\";\n    const footerHeight = footerHidden ? 0 : footer.offsetHeight;\n    scrollToBottomButton.style.bottom = `${footerHeight + SCROLL_TO_BOTTOM_EDGE_OFFSET}px`;\n  };\n  updateScrollToBottomButtonOffset();\n\n  const renderScrollToBottomButton = () => {\n    const hasLabel = Boolean(getScrollToBottomLabel());\n    scrollToBottomButton.setAttribute(\"aria-label\", getScrollToBottomLabel() || \"Jump to latest\");\n    scrollToBottomButton.title = getScrollToBottomLabel();\n    scrollToBottomButton.setAttribute(\"data-persona-scroll-to-bottom-has-label\", hasLabel ? \"true\" : \"false\");\n    scrollToBottomIcon.innerHTML = \"\";\n    const icon = renderLucideIcon(getScrollToBottomIconName(), \"14px\", \"currentColor\", 2);\n    if (icon) {\n      scrollToBottomIcon.appendChild(icon);\n      scrollToBottomIcon.style.display = \"\";\n    } else {\n      scrollToBottomIcon.style.display = \"none\";\n    }\n    scrollToBottomLabel.textContent = getScrollToBottomLabel();\n    scrollToBottomLabel.style.display = hasLabel ? \"\" : \"none\";\n  };\n  renderScrollToBottomButton();\n\n  // Initialized after composer plugins rebind footer DOM (see `bindComposerRefsFromFooter`)\n  let attachmentManager: AttachmentManager | null = null;\n\n  /** Wired after `handleMicButtonClick` is defined; used by `renderComposer` `onVoiceToggle`. */\n  let composerVoiceBridge: (() => void) | null = null;\n\n  // Plugin hook: renderHeader - allow plugins to provide custom header\n  const headerPlugin = plugins.find(p => p.renderHeader);\n  if (headerPlugin?.renderHeader) {\n    const customHeader = headerPlugin.renderHeader({\n      config,\n      defaultRenderer: () => {\n        const headerElements = buildHeader({ config, showClose: isPanelToggleable() });\n        attachHeaderToContainer(container, headerElements, config);\n        return headerElements.header;\n      },\n      onClose: () => setOpenState(false, \"user\")\n    });\n    if (customHeader) {\n      // Replace the default header with custom header\n      const existingHeader = container.querySelector('.persona-border-b-persona-divider');\n      if (existingHeader) {\n        existingHeader.replaceWith(customHeader);\n        header = customHeader;\n        // Keep the view's tracked header element in sync so a later\n        // header-layout rebuild (view.replaceHeader) targets the mounted node.\n        view.header.element = customHeader;\n      }\n    }\n  }\n\n  // Event stream toggle functions (lifted to outer scope for controller access)\n  const toggleEventStreamOn = () => {\n    if (!eventStreamBuffer) return;\n    eventStreamVisible = true;\n    if (!eventStreamView && eventStreamBuffer) {\n      eventStreamView = createEventStreamView({\n        buffer: eventStreamBuffer,\n        getFullHistory: () => eventStreamBuffer!.getAllFromStore(),\n        onClose: () => toggleEventStreamOff(),\n        config,\n        plugins,\n        getThroughput: () =>\n          throughputTracker?.getMetric() ?? { status: \"idle\" },\n      });\n    }\n    if (eventStreamView) {\n      body.style.display = \"none\";\n      footer.parentNode?.insertBefore(eventStreamView.element, footer);\n      eventStreamView.update();\n    }\n    if (eventStreamToggleBtn) {\n      eventStreamToggleBtn.style.boxShadow = `inset 0 0 0 1.5px ${HEADER_THEME_CSS.actionIconColor}`;\n      const activeClasses = config.features?.eventStream?.classNames?.toggleButtonActive;\n      if (activeClasses) activeClasses.split(/\\s+/).forEach(c => c && eventStreamToggleBtn!.classList.add(c));\n    }\n    // Start RAF-based update loop (throttled to ~200ms)\n    const rafLoop = () => {\n      if (!eventStreamVisible) return;\n      const now = Date.now();\n      if (now - eventStreamLastUpdate >= 200) {\n        eventStreamView?.update();\n        eventStreamLastUpdate = now;\n      }\n      eventStreamRAF = requestAnimationFrame(rafLoop);\n    };\n    eventStreamLastUpdate = 0;\n    eventStreamRAF = requestAnimationFrame(rafLoop);\n    syncScrollToBottomButton();\n    eventBus.emit(\"eventStream:opened\", { timestamp: Date.now() });\n  };\n\n  const toggleEventStreamOff = () => {\n    if (!eventStreamVisible) return;\n    eventStreamVisible = false;\n    if (eventStreamView) {\n      eventStreamView.element.remove();\n    }\n    body.style.display = \"\";\n    if (eventStreamToggleBtn) {\n      eventStreamToggleBtn.style.boxShadow = \"\";\n      const activeClasses = config.features?.eventStream?.classNames?.toggleButtonActive;\n      if (activeClasses) activeClasses.split(/\\s+/).forEach(c => c && eventStreamToggleBtn!.classList.remove(c));\n    }\n    // Cancel RAF update loop\n    if (eventStreamRAF !== null) {\n      cancelAnimationFrame(eventStreamRAF);\n      eventStreamRAF = null;\n    }\n    syncScrollToBottomButton();\n    eventBus.emit(\"eventStream:closed\", { timestamp: Date.now() });\n  };\n\n  // Event stream toggle button\n  let eventStreamToggleBtn: HTMLButtonElement | null = null;\n  if (showEventStreamToggle) {\n    const esClassNames = config.features?.eventStream?.classNames;\n    const toggleBtnClasses = \"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-opacity-80 persona-cursor-pointer persona-border-none persona-bg-transparent persona-p-1\" + (esClassNames?.toggleButton ? \" \" + esClassNames.toggleButton : \"\");\n    eventStreamToggleBtn = createElement(\"button\", toggleBtnClasses) as HTMLButtonElement;\n    eventStreamToggleBtn.style.width = \"28px\";\n    eventStreamToggleBtn.style.height = \"28px\";\n    eventStreamToggleBtn.style.color = HEADER_THEME_CSS.actionIconColor;\n    eventStreamToggleBtn.type = \"button\";\n    eventStreamToggleBtn.setAttribute(\"aria-label\", \"Event Stream\");\n    eventStreamToggleBtn.title = \"Event Stream\";\n    const activityIcon = renderLucideIcon(\"activity\", \"18px\", \"currentColor\", 1.5);\n    if (activityIcon) eventStreamToggleBtn.appendChild(activityIcon);\n\n    // Insert before clear chat button wrapper or close button wrapper\n    const clearChatWrapper = panelElements.clearChatButtonWrapper;\n    const closeWrapper = panelElements.closeButtonWrapper;\n    const insertBefore = clearChatWrapper || closeWrapper;\n    if (insertBefore && insertBefore.parentNode === header) {\n      header.insertBefore(eventStreamToggleBtn, insertBefore);\n    } else {\n      header.appendChild(eventStreamToggleBtn);\n    }\n\n    eventStreamToggleBtn.addEventListener(\"click\", () => {\n      if (eventStreamVisible) {\n        toggleEventStreamOff();\n      } else {\n        toggleEventStreamOn();\n      }\n    });\n  }\n\n  const ensureComposerAttachmentSurface = (rootFooter: HTMLElement) => {\n    const att = config.attachments;\n    if (!att?.enabled) return;\n    let previews =\n      rootFooter.querySelector<HTMLElement>(\"[data-persona-composer-attachment-previews]\") ??\n      rootFooter.querySelector<HTMLElement>(\".persona-attachment-previews\");\n    if (!previews) {\n      previews = createElement(\n        \"div\",\n        \"persona-attachment-previews persona-flex persona-flex-wrap persona-gap-2 persona-mb-2\"\n      );\n      previews.setAttribute(\"data-persona-composer-attachment-previews\", \"\");\n      previews.style.display = \"none\";\n      const form = rootFooter.querySelector(\"[data-persona-composer-form]\");\n      if (form?.parentNode) {\n        form.parentNode.insertBefore(previews, form);\n      } else {\n        rootFooter.insertBefore(previews, rootFooter.firstChild);\n      }\n    }\n    const hasFileInput =\n      rootFooter.querySelector<HTMLInputElement>(\"[data-persona-composer-attachment-input]\") ??\n      rootFooter.querySelector<HTMLInputElement>('input[type=\"file\"]');\n    if (!hasFileInput) {\n      const fileIn = createElement(\"input\") as HTMLInputElement;\n      fileIn.type = \"file\";\n      fileIn.setAttribute(\"data-persona-composer-attachment-input\", \"\");\n      fileIn.accept = (att.allowedTypes ?? ALL_SUPPORTED_MIME_TYPES).join(\",\");\n      fileIn.multiple = (att.maxFiles ?? 4) > 1;\n      fileIn.style.display = \"none\";\n      fileIn.setAttribute(\"aria-label\", att.buttonTooltipText ?? \"Attach files\");\n      rootFooter.appendChild(fileIn);\n    }\n  };\n\n  // Plugin hook: renderComposer - allow plugins to provide custom composer\n  const composerPlugin = plugins.find(p => p.renderComposer);\n  if (composerPlugin?.renderComposer) {\n    const composerCfg = config.composer;\n    const customComposer = composerPlugin.renderComposer({\n      config,\n      defaultRenderer: () => {\n        const composerElements = buildComposer({ config });\n        return composerElements.footer;\n      },\n      onSubmit: (text: string) => {\n        if (!session || session.isStreaming()) return;\n        const value = text.trim();\n        const hasAttachments = attachmentManager?.hasAttachments() ?? false;\n        if (!value && !hasAttachments) return;\n        // Mirror the default composer's auto-expand behavior so plugin\n        // composers do not silently submit while the panel stays collapsed.\n        maybeExpandComposerBar();\n        let contentParts: ContentPart[] | undefined;\n        if (hasAttachments) {\n          contentParts = [];\n          contentParts.push(...attachmentManager!.getContentParts());\n          if (value) {\n            contentParts.push(createTextPart(value));\n          }\n        }\n        session.sendMessage(value, { contentParts });\n        if (hasAttachments) {\n          attachmentManager!.clearAttachments();\n        }\n      },\n      streaming: false,\n      disabled: false,\n      openAttachmentPicker: () => {\n        attachmentInput?.click();\n      },\n      models: composerCfg?.models,\n      selectedModelId: composerCfg?.selectedModelId,\n      onModelChange: (modelId: string) => {\n        config.composer = { ...config.composer, selectedModelId: modelId };\n        // Sync to agent config so the next request uses the selected model\n        if (config.agent) {\n          config.agent = { ...config.agent, model: modelId };\n        }\n      },\n      onVoiceToggle:\n        config.voiceRecognition?.enabled === true\n          ? () => {\n              composerVoiceBridge?.();\n            }\n          : undefined\n    });\n    if (customComposer) {\n      // Replace the default footer with custom composer (keeps view.composer.footer in sync).\n      view.replaceComposer(customComposer);\n      footer = view.composer.footer;\n    }\n  }\n\n  const bindComposerRefsFromFooter = (rootFooter: HTMLElement) => {\n    // Prefer stable `data-persona-composer-*` refs (set by the default and\n    // pill builders); fall back to the legacy class selectors so custom\n    // plugin composers built before these refs existed still bind.\n    const pick = <T extends HTMLElement>(...selectors: string[]): T | null => {\n      for (const selector of selectors) {\n        const found = rootFooter.querySelector<T>(selector);\n        if (found) return found;\n      }\n      return null;\n    };\n\n    const form = rootFooter.querySelector<HTMLFormElement>(\"[data-persona-composer-form]\");\n    const ta = rootFooter.querySelector<HTMLTextAreaElement>(\"[data-persona-composer-input]\");\n    const sb = rootFooter.querySelector<HTMLButtonElement>(\"[data-persona-composer-submit]\");\n    const mic = rootFooter.querySelector<HTMLButtonElement>(\"[data-persona-composer-mic]\");\n    const st = rootFooter.querySelector<HTMLElement>(\"[data-persona-composer-status]\");\n    if (form) composerForm = form;\n    if (ta) textarea = ta;\n    if (sb) sendButton = sb;\n    if (mic) {\n      micButton = mic;\n      micButtonWrapper = mic.parentElement as HTMLElement | null;\n    }\n    if (st) statusText = st;\n    const sug = pick<HTMLElement>(\n      \"[data-persona-composer-suggestions]\",\n      \".persona-mb-3.persona-flex.persona-flex-wrap.persona-gap-2\"\n    );\n    if (sug) suggestions = sug;\n    const attBtn = pick<HTMLButtonElement>(\n      \"[data-persona-composer-attachment-button]\",\n      \".persona-attachment-button\"\n    );\n    if (attBtn) {\n      attachmentButton = attBtn;\n      attachmentButtonWrapper = attBtn.parentElement as HTMLElement | null;\n    }\n    attachmentInput = pick<HTMLInputElement>(\n      \"[data-persona-composer-attachment-input]\",\n      'input[type=\"file\"]'\n    );\n    attachmentPreviewsContainer = pick<HTMLElement>(\n      \"[data-persona-composer-attachment-previews]\",\n      \".persona-attachment-previews\"\n    );\n    const ar = pick<HTMLElement>(\n      \"[data-persona-composer-actions]\",\n      \".persona-widget-composer .persona-flex.persona-items-center.persona-justify-between\"\n    );\n    if (ar) _actionsRow = ar;\n  };\n  ensureComposerAttachmentSurface(footer);\n  bindComposerRefsFromFooter(footer);\n\n  // Apply contentMaxWidth to composer form, suggestions, and attachment\n  // previews if configured. In composer-bar mode, fall back to\n  // `composerBar.contentMaxWidth` (default `720px`) when no explicit\n  // `layout.contentMaxWidth` is set, so the expanded panel's content\n  // centers horizontally without the host having to wire it up.\n  const contentMaxWidth =\n    config.layout?.contentMaxWidth ??\n    (isComposerBar() ? config.launcher?.composerBar?.contentMaxWidth ?? \"720px\" : undefined);\n  if (contentMaxWidth) {\n    messagesWrapper.style.maxWidth = contentMaxWidth;\n    messagesWrapper.style.marginLeft = \"auto\";\n    messagesWrapper.style.marginRight = \"auto\";\n    messagesWrapper.style.width = \"100%\";\n  }\n  // The pill IS the composer in composer-bar mode and should match the\n  // wrapper's responsive width (50vw / 70vw / 90vw), not be capped by\n  // contentMaxWidth (which is a centered-column convention for the\n  // expanded panel's body, not the pill input itself).\n  if (contentMaxWidth && composerForm && !isComposerBar()) {\n    composerForm.style.maxWidth = contentMaxWidth;\n    composerForm.style.marginLeft = \"auto\";\n    composerForm.style.marginRight = \"auto\";\n  }\n  if (contentMaxWidth && suggestions && !isComposerBar()) {\n    suggestions.style.maxWidth = contentMaxWidth;\n    suggestions.style.marginLeft = \"auto\";\n    suggestions.style.marginRight = \"auto\";\n  }\n  if (contentMaxWidth && attachmentPreviewsContainer && !isComposerBar()) {\n    attachmentPreviewsContainer.style.maxWidth = contentMaxWidth;\n    attachmentPreviewsContainer.style.marginLeft = \"auto\";\n    attachmentPreviewsContainer.style.marginRight = \"auto\";\n  }\n\n  if (config.attachments?.enabled && attachmentInput && attachmentPreviewsContainer) {\n    attachmentManager = AttachmentManager.fromConfig(config.attachments);\n    attachmentManager.setPreviewsContainer(attachmentPreviewsContainer);\n    attachmentInput.addEventListener(\"change\", (e) => {\n      const target = e.target as HTMLInputElement;\n      attachmentManager?.handleFileSelect(target.files);\n      target.value = \"\";\n    });\n\n    const dropCfg = config.attachments.dropOverlay;\n    const overlay = buildDropOverlay(dropCfg);\n    container.appendChild(overlay);\n  }\n\n  // Slot system: allow custom content injection into specific regions\n  const renderSlots = () => {\n    const slots = config.layout?.slots ?? {};\n    \n    // Helper to get default slot content\n    const getDefaultSlotContent = (slot: WidgetLayoutSlot): HTMLElement | null => {\n      switch (slot) {\n        case \"body-top\":\n          // Default: the intro card\n          return container.querySelector(\".persona-rounded-2xl.persona-bg-persona-surface.persona-p-6\") as HTMLElement || null;\n        case \"messages\":\n          return messagesWrapper;\n        case \"footer-top\":\n          return suggestions;\n        case \"composer\":\n          return composerForm;\n        case \"footer-bottom\":\n          return statusText;\n        default:\n          return null;\n      }\n    };\n\n    // Helper to insert content into slot region\n    const insertSlotContent = (slot: WidgetLayoutSlot, element: HTMLElement) => {\n      switch (slot) {\n        case \"header-left\":\n        case \"header-center\":\n        case \"header-right\":\n          // Header slots - prepend/append to header\n          if (slot === \"header-left\") {\n            header.insertBefore(element, header.firstChild);\n          } else if (slot === \"header-right\") {\n            header.appendChild(element);\n          } else {\n            // header-center: insert after icon/title\n            const titleSection = header.querySelector(\".persona-flex-col\");\n            if (titleSection) {\n              titleSection.parentNode?.insertBefore(element, titleSection.nextSibling);\n            } else {\n              header.appendChild(element);\n            }\n          }\n          break;\n        case \"body-top\": {\n          // Replace or prepend to body\n          const introCard = body.querySelector(\".persona-rounded-2xl.persona-bg-persona-surface.persona-p-6\");\n          if (introCard) {\n            introCard.replaceWith(element);\n          } else {\n            body.insertBefore(element, body.firstChild);\n          }\n          break;\n        }\n        case \"body-bottom\":\n          // Append after messages wrapper\n          body.appendChild(element);\n          break;\n        case \"footer-top\":\n          // Replace suggestions area\n          suggestions.replaceWith(element);\n          break;\n        case \"footer-bottom\":\n          // Replace or append after status text\n          statusText.replaceWith(element);\n          break;\n        default:\n          // For other slots, just append to appropriate container\n          break;\n      }\n    };\n\n    // Process each configured slot\n    for (const [slotName, renderer] of Object.entries(slots) as [WidgetLayoutSlot, SlotRenderer][]) {\n      if (renderer) {\n        try {\n          const slotElement = renderer({\n            config,\n            defaultContent: () => getDefaultSlotContent(slotName)\n          });\n          if (slotElement) {\n            insertSlotContent(slotName, slotElement);\n          }\n        } catch (error) {\n          if (typeof console !== \"undefined\") {\n            // eslint-disable-next-line no-console\n            console.error(`[AgentWidget] Error rendering slot \"${slotName}\":`, error);\n          }\n        }\n      }\n    }\n  };\n\n  // Render custom slots\n  renderSlots();\n\n  // Add event delegation for reasoning and tool bubble expansion\n  // This handles clicks even after idiomorph morphs the DOM\n  const handleBubbleExpansion = (event: Event) => {\n    const target = event.target as HTMLElement;\n    \n    // Check if the click/keypress is on an expand header button\n    const headerButton = target.closest('button[data-expand-header=\"true\"]') as HTMLElement;\n    if (!headerButton) return;\n    \n    // Find the parent bubble element\n    const bubble = headerButton.closest('.persona-reasoning-bubble, .persona-tool-bubble, .persona-approval-bubble') as HTMLElement;\n    if (!bubble) return;\n    \n    // Get message ID from bubble\n    const messageId = bubble.getAttribute('data-message-id');\n    if (!messageId) return;\n    \n    const bubbleType = headerButton.getAttribute('data-bubble-type');\n    \n    // Toggle expansion state\n    if (bubbleType === 'reasoning') {\n      if (reasoningExpansionState.has(messageId)) {\n        reasoningExpansionState.delete(messageId);\n      } else {\n        reasoningExpansionState.add(messageId);\n      }\n      updateReasoningBubbleUI(messageId, bubble);\n    } else if (bubbleType === 'tool') {\n      if (toolExpansionState.has(messageId)) {\n        toolExpansionState.delete(messageId);\n      } else {\n        toolExpansionState.add(messageId);\n      }\n      updateToolBubbleUI(messageId, bubble, config);\n    } else if (bubbleType === 'approval') {\n      const approvalConfig = config.approval !== false ? config.approval : undefined;\n      const defaultExpanded = (approvalConfig?.detailsDisplay ?? 'collapsed') === 'expanded';\n      const expanded = approvalDetailsExpansionState.get(messageId) ?? defaultExpanded;\n      approvalDetailsExpansionState.set(messageId, !expanded);\n      updateApprovalDetailsUI(messageId, bubble, config);\n    }\n    // Invalidate cached wrapper so next render rebuilds with current expansion state\n    messageCache.delete(messageId);\n  };\n\n  // Attach event listeners to messagesWrapper for event delegation\n  messagesWrapper.addEventListener('pointerdown', (event) => {\n    const target = event.target as HTMLElement;\n    if (target.closest('button[data-expand-header=\"true\"]')) {\n      event.preventDefault();\n      handleBubbleExpansion(event);\n    }\n  });\n\n  messagesWrapper.addEventListener('keydown', (event) => {\n    const target = event.target as HTMLElement;\n    if ((event.key === 'Enter' || event.key === ' ') && target.closest('button[data-expand-header=\"true\"]')) {\n      event.preventDefault();\n      handleBubbleExpansion(event);\n    }\n  });\n\n  // Normalize manual (triple-click + Ctrl/Cmd-C) copies of message text. The\n  // browser serializes the DOM selection, and block-level markdown elements\n  // (<p>, <li>, <pre>, …) emit surrounding newlines — so a single-message copy\n  // arrives with stray trailing/leading blank lines. Rewrite the clipboard's\n  // plain text to the trimmed selection so the buffer matches what was visibly\n  // highlighted. (The Copy action button is unaffected; it uses message.content.)\n  messagesWrapper.addEventListener('copy', (event) => {\n    const { clipboardData } = event;\n    if (!clipboardData) return;\n    const root = messagesWrapper.getRootNode() as { getSelection?: () => Selection | null };\n    const selection =\n      typeof root.getSelection === 'function' ? root.getSelection() : window.getSelection();\n    if (!selection || selection.isCollapsed) return;\n    const raw = selection.toString();\n    const normalized = normalizeCopiedSelectionText(raw);\n    if (!normalized || normalized === raw) return;\n    clipboardData.setData('text/plain', normalized);\n    event.preventDefault();\n  });\n\n  // Add event delegation for message action buttons (upvote, downvote, copy)\n  // This handles clicks even after idiomorph morphs the DOM and strips inline listeners\n  const messageVoteState = new Map<string, \"upvote\" | \"downvote\">();\n\n  // Read-aloud (text-to-speech) button state. The ReadAloudController in the\n  // session is the source of truth; these mirror its last-known state so the\n  // button visuals can be re-applied after every render/morph (which would\n  // otherwise revert the swapped icon to the default \"volume-2\").\n  let readAloudActiveId: string | null = null;\n  let readAloudActiveState: ReadAloudState = \"idle\";\n\n  const READ_ALOUD_ICONS: Record<ReadAloudState, { icon: string; label: string }> = {\n    idle: { icon: \"volume-2\", label: \"Read aloud\" },\n    loading: { icon: \"loader-circle\", label: \"Loading…\" },\n    playing: { icon: \"pause\", label: \"Pause\" },\n    paused: { icon: \"play\", label: \"Resume\" },\n  };\n\n  const applyReadAloudButton = (btn: HTMLElement, state: ReadAloudState) => {\n    const { icon, label } = READ_ALOUD_ICONS[state];\n    btn.setAttribute(\"aria-label\", label);\n    btn.title = label;\n    btn.setAttribute(\"aria-pressed\", state === \"idle\" ? \"false\" : \"true\");\n    btn.classList.toggle(\"persona-message-action-active\", state !== \"idle\");\n    btn.classList.toggle(\"persona-message-action-loading\", state === \"loading\");\n    const svg = renderLucideIcon(icon, 14, \"currentColor\", 2);\n    if (svg) {\n      btn.innerHTML = \"\";\n      btn.appendChild(svg);\n    }\n  };\n\n  // Re-apply the current read-aloud state to every read-aloud button in the\n  // thread. Called on state change and after each render so a button that is\n  // playing/paused keeps its icon across DOM morphs.\n  const refreshReadAloudButtons = () => {\n    const buttons = messagesWrapper.querySelectorAll<HTMLElement>('[data-action=\"read-aloud\"]');\n    buttons.forEach((btn) => {\n      const container = btn.closest(\"[data-actions-for]\");\n      const id = container?.getAttribute(\"data-actions-for\") ?? null;\n      const state: ReadAloudState = id && id === readAloudActiveId ? readAloudActiveState : \"idle\";\n      applyReadAloudButton(btn, state);\n    });\n  };\n\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const actionBtn = target.closest('.persona-message-action-btn[data-action]') as HTMLElement;\n    if (!actionBtn) return;\n\n    event.preventDefault();\n    event.stopPropagation();\n\n    const actionsContainer = actionBtn.closest('[data-actions-for]') as HTMLElement;\n    if (!actionsContainer) return;\n\n    const messageId = actionsContainer.getAttribute('data-actions-for');\n    if (!messageId) return;\n\n    const action = actionBtn.getAttribute('data-action');\n\n    if (action === 'copy') {\n      const messages = session.getMessages();\n      const message = messages.find(m => m.id === messageId);\n      if (message && messageActionCallbacks.onCopy) {\n        // Copy to clipboard\n        const textToCopy = message.content || \"\";\n        navigator.clipboard.writeText(textToCopy).then(() => {\n          // Show success feedback - swap icon temporarily\n          actionBtn.classList.add(\"persona-message-action-success\");\n          const checkIcon = renderLucideIcon(\"check\", 14, \"currentColor\", 2);\n          if (checkIcon) {\n            actionBtn.innerHTML = \"\";\n            actionBtn.appendChild(checkIcon);\n          }\n          setTimeout(() => {\n            actionBtn.classList.remove(\"persona-message-action-success\");\n            const originalIcon = renderLucideIcon(\"copy\", 14, \"currentColor\", 2);\n            if (originalIcon) {\n              actionBtn.innerHTML = \"\";\n              actionBtn.appendChild(originalIcon);\n            }\n          }, 2000);\n        }).catch((err) => {\n          if (typeof console !== \"undefined\") {\n            // eslint-disable-next-line no-console\n            console.error(\"[AgentWidget] Failed to copy message:\", err);\n          }\n        });\n        messageActionCallbacks.onCopy(message);\n      }\n    } else if (action === 'read-aloud') {\n      // Toggle play/pause/resume; ReadAloudController drives the engine and\n      // notifies onReadAloudChange, which refreshes the button icon.\n      session.toggleReadAloud(messageId);\n    } else if (action === 'upvote' || action === 'downvote') {\n      const currentVote = messageVoteState.get(messageId) ?? null;\n      const wasActive = currentVote === action;\n      const iconName = action === 'upvote' ? 'thumbs-up' : 'thumbs-down';\n\n      if (wasActive) {\n        // Toggle off: revert to outline icon\n        messageVoteState.delete(messageId);\n        actionBtn.classList.remove(\"persona-message-action-active\");\n        const outlineIcon = renderLucideIcon(iconName, 14, \"currentColor\", 2);\n        if (outlineIcon) {\n          actionBtn.innerHTML = \"\";\n          actionBtn.appendChild(outlineIcon);\n        }\n      } else {\n        // Clear opposite vote button and revert its icon\n        const oppositeAction = action === 'upvote' ? 'downvote' : 'upvote';\n        const oppositeBtn = actionsContainer.querySelector(`[data-action=\"${oppositeAction}\"]`);\n        if (oppositeBtn) {\n          oppositeBtn.classList.remove(\"persona-message-action-active\");\n          const oppositeIconName = oppositeAction === 'upvote' ? 'thumbs-up' : 'thumbs-down';\n          const outlineIcon = renderLucideIcon(oppositeIconName, 14, \"currentColor\", 2);\n          if (outlineIcon) {\n            oppositeBtn.innerHTML = \"\";\n            oppositeBtn.appendChild(outlineIcon);\n          }\n        }\n\n        messageVoteState.set(messageId, action);\n        actionBtn.classList.add(\"persona-message-action-active\");\n\n        // Swap to filled icon\n        const filledIcon = renderLucideIcon(iconName, 14, \"currentColor\", 2);\n        if (filledIcon) {\n          filledIcon.setAttribute(\"fill\", \"currentColor\");\n          actionBtn.innerHTML = \"\";\n          actionBtn.appendChild(filledIcon);\n        }\n\n        // Pop animation\n        actionBtn.classList.remove(\"persona-message-action-pop\");\n        void actionBtn.offsetWidth; // force reflow to restart animation\n        actionBtn.classList.add(\"persona-message-action-pop\");\n\n        // Trigger feedback\n        const messages = session.getMessages();\n        const message = messages.find(m => m.id === messageId);\n        if (message && messageActionCallbacks.onFeedback) {\n          messageActionCallbacks.onFeedback({\n            type: action,\n            messageId: message.id,\n            message\n          });\n        }\n      }\n    }\n  });\n\n  // Add event delegation for approval action buttons (approve/deny)\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const approvalButton = target.closest('button[data-approval-action]') as HTMLElement;\n    if (!approvalButton) return;\n\n    event.preventDefault();\n    event.stopPropagation();\n\n    const approvalBubble = approvalButton.closest('.persona-approval-bubble') as HTMLElement;\n    if (!approvalBubble) return;\n\n    const messageId = approvalBubble.getAttribute('data-message-id');\n    if (!messageId) return;\n\n    const action = approvalButton.getAttribute('data-approval-action') as 'approve' | 'deny';\n    if (!action) return;\n\n    const decision = action === 'approve' ? 'approved' as const : 'denied' as const;\n\n    // Find the approval message\n    const messages = session.getMessages();\n    const approvalMessage = messages.find(m => m.id === messageId);\n    if (!approvalMessage?.approval) return;\n\n    // Disable buttons immediately for responsive UI\n    const buttonsContainer = approvalBubble.querySelector('[data-approval-buttons]') as HTMLElement;\n    if (buttonsContainer) {\n      const buttons = buttonsContainer.querySelectorAll('button');\n      buttons.forEach(btn => {\n        (btn as HTMLButtonElement).disabled = true;\n        btn.style.opacity = '0.5';\n        btn.style.cursor = 'not-allowed';\n      });\n    }\n\n    // WebMCP gate approvals resolve a local Promise the bridge is parked on\n    // (no server round-trip); server-driven approvals call the API. The\n    // `toolType` marker set in `requestWebMcpApproval` discriminates the two.\n    if (approvalMessage.approval.toolType === \"webmcp\") {\n      session.resolveWebMcpApproval(messageId, decision);\n    } else {\n      session.resolveApproval(approvalMessage.approval, decision);\n    }\n  });\n\n  let artifactPaneApi: ArtifactPaneApi | null = null;\n  let artifactPanelResizeObs: ResizeObserver | null = null;\n  // Re-runs panel chrome when the split-chrome mode flips (pane\n  // open/close/appearance change). Assigned once applyFullHeightStyles exists.\n  let syncPanelChrome: () => void = () => {};\n  let appliedSplitMode: \"none\" | \"welded\" | \"detached\" = \"none\";\n  // Resolved welded outer-right radius stashed by applyFullHeightStyles and\n  // applied in syncPanelChrome (after applyArtifactPaneAppearance, which no\n  // longer manages the var, so the two never fight). Null clears it.\n  let weldedOuterRadius: string | null = null;\n  let lastArtifactsState: {\n    artifacts: PersonaArtifactRecord[];\n    selectedId: string | null;\n  } = { artifacts: [], selectedId: null };\n  let artifactsPaneUserHidden = false;\n  // Runtime-only expand state: pane fills the split root and the chat column\n  // hides. Reset whenever the pane stops being visible (syncArtifactPane).\n  let artifactPaneExpanded = false;\n  // Set when the expansion came from an inline block's Expand button: that is\n  // an explicit \"fullscreen this file\" request, so it survives the\n  // showExpandToggle gate below (which otherwise collapses the pane for hosts\n  // without the toolbar toggle). Cleared whenever the pane collapses or hides.\n  let artifactPaneExpandedPinned = false;\n  // Whether the user explicitly opened the pane (card click, inline Expand,\n  // showArtifacts(), programmatic upsert). Auto-open is otherwise reserved for\n  // artifacts whose resolved display mode is \"panel\": \"card\" keeps the card as\n  // the only affordance and \"inline\" renders in the transcript. \"inline\" no\n  // longer *auto*-opens the pane, but its Expand control is a deliberate,\n  // user-driven open (setting artifactsPaneUserOpened, same as a card click);\n  // every mode still writes the session artifact registry (download /\n  // getArtifacts / hydration).\n  let artifactsPaneUserOpened = false;\n  const artifactPaneCanShow = () =>\n    artifactsPaneUserOpened ||\n    lastArtifactsState.artifacts.some(\n      (a) =>\n        resolveArtifactDisplayMode(config.features?.artifacts, a.artifactType) === \"panel\"\n    );\n  const artifactPaneVisible = () =>\n    lastArtifactsState.artifacts.length > 0 &&\n    !artifactsPaneUserHidden &&\n    artifactPaneCanShow();\n  const sessionRef: { current: AgentWidgetSession | null } = { current: null };\n\n  // Resolve an artifact's content for card actions (download + custom actions).\n  // Tries the live session registry first, then falls back to the content\n  // persisted in the card message's rawContent props (session state is gone\n  // after a page refresh). `fromEl` is the clicked element inside the card.\n  const resolveCardArtifactContent = (\n    fromEl: HTMLElement,\n    artifactId: string\n  ): { markdown?: string; title: string; file?: PersonaArtifactFileMeta; artifactType: string } => {\n    const artifact = session.getArtifactById(artifactId);\n    let markdown = artifact?.markdown;\n    let title = artifact?.title || 'artifact';\n    let file: PersonaArtifactFileMeta | undefined = artifact?.file;\n    let artifactType: string = artifact?.artifactType ?? 'markdown';\n    if (!markdown) {\n      // Match both reference cards and inline blocks: inline hydration embeds\n      // the markdown / file source in the same message rawContent, so the parse\n      // below resolves content for inline copy / custom actions after a refresh.\n      const cardEl = fromEl.closest('[data-open-artifact], [data-artifact-inline]');\n      const msgEl = cardEl?.closest('[data-message-id]');\n      const msgId = msgEl?.getAttribute('data-message-id');\n      if (msgId) {\n        const msgs = session.getMessages();\n        const msg = msgs.find(m => m.id === msgId);\n        if (msg?.rawContent) {\n          try {\n            const parsed = JSON.parse(msg.rawContent);\n            markdown = parsed?.props?.markdown;\n            title = parsed?.props?.title || title;\n            if (parsed?.props?.file && typeof parsed.props.file === 'object') {\n              file = parsed.props.file as PersonaArtifactFileMeta;\n            }\n            if (!artifact && typeof parsed?.props?.artifactType === 'string') {\n              artifactType = parsed.props.artifactType;\n            }\n          } catch { /* ignore */ }\n        }\n      }\n    }\n    return { markdown, title, file, artifactType };\n  };\n\n  // Click delegation for artifact download buttons\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const dlBtn = target.closest('[data-download-artifact]') as HTMLElement;\n    if (!dlBtn) return;\n    event.preventDefault();\n    event.stopPropagation();\n    const artifactId = dlBtn.getAttribute('data-download-artifact');\n    if (!artifactId) return;\n    // Let integrator intercept\n    const dlPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'download', artifactId });\n    if (dlPrevented === true) return;\n    const { markdown, title, file } = resolveCardArtifactContent(dlBtn, artifactId);\n    if (!markdown) return;\n    // File artifacts download the raw unfenced source under their real name/MIME;\n    // non-file markdown artifacts keep the legacy `<title>.md` / text/markdown path.\n    const { filename, mime, content } = downloadInfoFor({ title, markdown, file });\n    const blob = new Blob([content], { type: mime });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = filename;\n    a.click();\n    URL.revokeObjectURL(url);\n  });\n\n  // Click delegation for integrator-supplied card action buttons. Actions are\n  // looked up by id from fresh config at click time so live config updates\n  // apply. Like the download listener, its stopPropagation() cannot block the\n  // card-open listener on the same element, so that listener skips these too.\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const actionBtn = target.closest('[data-artifact-custom-action]') as HTMLElement;\n    if (!actionBtn) return;\n    event.preventDefault();\n    event.stopPropagation();\n    const actionId = actionBtn.getAttribute('data-artifact-custom-action');\n    if (!actionId) return;\n    // The same attribute serves cards and inline chrome; resolve which surface\n    // the button lives in so the action comes from the matching config list and\n    // the artifact id from the matching container attribute.\n    const inlineEl = actionBtn.closest('[data-artifact-inline]');\n    const cardEl = inlineEl ? null : actionBtn.closest('[data-open-artifact]');\n    const artifactId = inlineEl\n      ? inlineEl.getAttribute('data-artifact-inline')\n      : cardEl?.getAttribute('data-open-artifact') ?? null;\n    const actionList = inlineEl\n      ? config.features?.artifacts?.inlineActions\n      : config.features?.artifacts?.cardActions;\n    const action = actionList?.find((a) => a.id === actionId);\n    if (!action) return;\n    const { markdown, title, file, artifactType } = resolveCardArtifactContent(actionBtn, artifactId ?? '');\n    const ctx: PersonaArtifactActionContext = { artifactId, title, artifactType, markdown, file };\n    try {\n      void Promise.resolve(action.onClick(ctx)).catch(() => {});\n    } catch {\n      /* ignore */\n    }\n  });\n\n  // Click delegation for the inline chrome Copy button. Like the download /\n  // custom-action listeners, it resolves content from the live registry first,\n  // then falls back to the persisted inline props after a refresh, and its\n  // stopPropagation() cannot block a second listener on the same element (there\n  // is none here, but the pattern is kept consistent).\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const copyBtn = target.closest('[data-copy-artifact]') as HTMLElement;\n    if (!copyBtn) return;\n    event.preventDefault();\n    event.stopPropagation();\n    const artifactId = copyBtn.getAttribute('data-copy-artifact');\n    if (!artifactId) return;\n    // Prefer the live record (covers component JSON); fall back to the persisted\n    // markdown / file source parsed from the inline block's message rawContent.\n    const artifact = session.getArtifactById(artifactId);\n    let text = '';\n    if (artifact) {\n      text = artifactCopyText(artifact);\n    } else {\n      const { markdown, file, artifactType } = resolveCardArtifactContent(copyBtn, artifactId);\n      if (artifactType === 'markdown') {\n        text = artifactCopyText({\n          id: artifactId,\n          artifactType: 'markdown',\n          status: 'complete',\n          markdown: markdown ?? '',\n          ...(file ? { file } : {})\n        });\n      }\n    }\n    if (!text) return;\n    navigator.clipboard.writeText(text).then(() => {\n      // Lightweight feedback: swap the copy glyph for a check briefly.\n      const checkIcon = renderLucideIcon('check', 16, 'currentColor', 2);\n      if (checkIcon) {\n        copyBtn.replaceChildren(checkIcon);\n        setTimeout(() => {\n          const copyIcon = renderLucideIcon('copy', 16, 'currentColor', 2);\n          if (copyIcon) copyBtn.replaceChildren(copyIcon);\n        }, 1500);\n      }\n    }).catch(() => { /* ignore */ });\n  });\n\n  // Click delegation for the inline chrome Expand button: open this artifact in\n  // the pane. Fires onArtifactAction({ type: \"open\" }) first so hosts can\n  // intercept, then mirrors the card-open path — except the pane opens\n  // expanded (fullscreen), never split: the inline block already shows the\n  // full preview at chat width, so a split pane would only duplicate it, and\n  // the click means \"expand this file\". The pinned flag keeps it expanded\n  // even when layout.showExpandToggle is off; Close is the exit there.\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    const expandBtn = target.closest('[data-expand-artifact-inline]') as HTMLElement;\n    if (!expandBtn) return;\n    event.preventDefault();\n    event.stopPropagation();\n    const artifactId = expandBtn.getAttribute('data-expand-artifact-inline');\n    if (!artifactId) return;\n    const openPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'open', artifactId });\n    if (openPrevented === true) return;\n    artifactsPaneUserHidden = false;\n    // Expand is an explicit open: it overrides the \"inline\" auto-open\n    // suppression for as long as artifacts exist (same as a card click).\n    artifactsPaneUserOpened = true;\n    artifactPaneExpanded = true;\n    artifactPaneExpandedPinned = true;\n    session.selectArtifact(artifactId);\n    syncArtifactPane();\n  });\n\n  // Click delegation for artifact reference cards\n  messagesWrapper.addEventListener('click', (event) => {\n    const target = event.target as HTMLElement;\n    // Download and custom-action clicks are handled by the listeners above;\n    // their stopPropagation() cannot block a second listener on the same\n    // element, so skip them here explicitly or the card would also open the panel.\n    if (target.closest('[data-download-artifact]')) return;\n    if (target.closest('[data-artifact-custom-action]')) return;\n    const card = target.closest('[data-open-artifact]') as HTMLElement;\n    if (!card) return;\n    const artifactId = card.getAttribute('data-open-artifact');\n    if (!artifactId) return;\n    // Let integrator intercept\n    const openPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'open', artifactId });\n    if (openPrevented === true) return;\n    event.preventDefault();\n    event.stopPropagation();\n    artifactsPaneUserHidden = false;\n    // Card click is an explicit open: it overrides the \"card\"/\"inline\"\n    // auto-open suppression for as long as artifacts exist.\n    artifactsPaneUserOpened = true;\n    session.selectArtifact(artifactId);\n    syncArtifactPane();\n  });\n\n  // Keyboard support for artifact cards and the clip-mode inline expand hitbox\n  // (both expose a data-* attribute on a focusable element; Enter/Space forward\n  // to the element's own click, which the delegated click handlers above own).\n  messagesWrapper.addEventListener('keydown', (event) => {\n    if (event.key !== 'Enter' && event.key !== ' ') return;\n    const target = event.target as HTMLElement;\n    if (\n      !target.hasAttribute('data-open-artifact') &&\n      !target.hasAttribute('data-expand-artifact-inline')\n    )\n      return;\n    event.preventDefault();\n    target.click();\n  });\n\n  // --- ask_user_question sheet interaction ---\n  // Event delegation for the answer-pill sheet that mounts in the composer\n  // overlay. Handles pill pick (single), multi-select toggle + submit, free-\n  // text pill expansion + submit, and dismissal. Selection becomes a regular\n  // user message via session.sendMessage so the agent resumes on the next turn.\n  const askUserOverlay = panelElements.composerOverlay;\n\n  const submitAskUserAnswer = (\n    sheet: HTMLElement,\n    text: string,\n    meta: {\n      source: \"pick\" | \"multi\" | \"free-text\" | \"submit-all\";\n      values?: string[];\n      structured?: Record<string, string | string[]>;\n    }\n  ): void => {\n    const trimmed = text.trim();\n    if (!trimmed || !sessionRef.current) return;\n    const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n    const isFreeText = meta.source === \"free-text\";\n\n    // Dispatch before removing the sheet so listeners can still query DOM state.\n    mount.dispatchEvent(\n      new CustomEvent(\"persona:askUserQuestion:answered\", {\n        detail: {\n          toolUseId: toolCallId,\n          answer: trimmed,\n          answers: meta.structured,\n          values: meta.values ?? (meta.source === \"multi\" ? trimmed.split(\", \") : [trimmed]),\n          isFreeText,\n          source: meta.source,\n        },\n        bubbles: true,\n        composed: true,\n      })\n    );\n\n    removeAskUserQuestionSheet(askUserOverlay, toolCallId);\n\n    // Branch: LOCAL-tool pause (step_await) resumes via /resume with structured\n    // toolOutputs; legacy path sends as a plain user message.\n    const sourceMessage = sessionRef.current\n      .getMessages()\n      .find((m) => m.toolCall?.id === toolCallId);\n    if (sourceMessage?.agentMetadata?.awaitingLocalTool) {\n      sessionRef.current.resolveAskUserQuestion(sourceMessage, meta.structured ?? trimmed);\n    } else {\n      sessionRef.current.sendMessage(trimmed);\n    }\n  };\n\n  /**\n   * Persist in-progress grouped-question answers + page index back to the\n   * source message so a refresh restores the user's spot.\n   */\n  const persistGroupedProgress = (sheet: HTMLElement): void => {\n    const session = sessionRef.current;\n    if (!session) return;\n    const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n    const sourceMessage = session.getMessages().find((m) => m.toolCall?.id === toolCallId);\n    if (!sourceMessage) return;\n    session.persistAskUserQuestionProgress(sourceMessage, {\n      answers: buildStructuredAnswers(sheet, sourceMessage),\n      currentIndex: getCurrentIndex(sheet),\n    });\n  };\n\n  /**\n   * Build a one-line summary string for the legacy `answer` field on the\n   * answered event when submit-all fires from a grouped sheet.\n   */\n  const stringifyStructured = (answers: Record<string, string | string[]>): string => {\n    return Object.entries(answers)\n      .map(([q, v]) => `${q}: ${Array.isArray(v) ? v.join(\", \") : v}`)\n      .join(\" | \");\n  };\n\n  /**\n   * If `groupedAutoAdvance` is enabled (default) and we're not on the final\n   * page, advance one step. The final page never auto-submits: users always\n   * confirm with an explicit Submit-all click so they can review.\n   */\n  const maybeAutoAdvance = (sheet: HTMLElement): void => {\n    if (config.features?.askUserQuestion?.groupedAutoAdvance === false) return;\n    const idx = getCurrentIndex(sheet);\n    const count = getQuestionCount(sheet);\n    if (idx >= count - 1) return;\n    const sourceMessage = sessionRef.current\n      ?.getMessages()\n      .find((m) => m.toolCall?.id === sheet.getAttribute(\"data-tool-call-id\"));\n    if (!sourceMessage) return;\n    navigateToPage(sheet, sourceMessage, config, idx + 1);\n    persistGroupedProgress(sheet);\n  };\n\n  askUserOverlay.addEventListener(\"click\", (event) => {\n    const target = event.target as HTMLElement;\n    const trigger = target.closest<HTMLElement>(\"[data-ask-user-action]\");\n    if (!trigger) return;\n    const sheet = trigger.closest<HTMLElement>(\"[data-persona-ask-sheet-for]\");\n    if (!sheet) return;\n\n    const action = trigger.getAttribute(\"data-ask-user-action\");\n    event.preventDefault();\n    event.stopPropagation();\n\n    if (action === \"dismiss\") {\n      const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n      mount.dispatchEvent(\n        new CustomEvent(\"persona:askUserQuestion:dismissed\", {\n          detail: { toolUseId: toolCallId },\n          bubbles: true,\n          composed: true,\n        })\n      );\n      removeAskUserQuestionSheet(askUserOverlay, toolCallId);\n\n      // Best-effort: if this sheet corresponds to a LOCAL-awaiting tool,\n      // unblock the paused execution with a sentinel answer so the server\n      // doesn't sit in waiting_for_local forever. Fire-and-forget: errors\n      // are surfaced to the onError callback. Flip the answered flag first\n      // so a racing render pass doesn't re-mount the sheet mid-dismissal.\n      const sourceMessage = sessionRef.current\n        ?.getMessages()\n        .find((m) => m.toolCall?.id === toolCallId);\n      if (sourceMessage?.agentMetadata?.awaitingLocalTool) {\n        sessionRef.current?.markAskUserQuestionResolved(sourceMessage);\n        sessionRef.current?.resolveAskUserQuestion(sourceMessage, \"(dismissed)\");\n      }\n      return;\n    }\n\n    if (action === \"pick\") {\n      const label = trigger.getAttribute(\"data-option-label\");\n      if (!label) return;\n      const multiSelect = sheet.getAttribute(\"data-multi-select\") === \"true\";\n      const grouped = isGroupedSheet(sheet);\n\n      if (grouped && multiSelect) {\n        const stored = readAnswersFromSheet(sheet)[getCurrentIndex(sheet)];\n        const set = new Set<string>(Array.isArray(stored) ? stored : []);\n        if (set.has(label)) set.delete(label);\n        else set.add(label);\n        setCurrentAnswer(sheet, Array.from(set));\n        persistGroupedProgress(sheet);\n        return;\n      }\n\n      if (grouped) {\n        setCurrentAnswer(sheet, label);\n        persistGroupedProgress(sheet);\n        maybeAutoAdvance(sheet);\n        return;\n      }\n\n      // 1-question modes: preserve original UX.\n      if (multiSelect) {\n        const pressed = trigger.getAttribute(\"aria-pressed\") === \"true\";\n        trigger.setAttribute(\"aria-pressed\", pressed ? \"false\" : \"true\");\n        trigger.classList.toggle(\"persona-ask-pill-selected\", !pressed);\n        const submitBtn = sheet.querySelector<HTMLButtonElement>(\n          '[data-ask-user-action=\"submit-multi\"]'\n        );\n        if (submitBtn) {\n          submitBtn.disabled = getSelectedLabels(sheet).length === 0;\n        }\n        return;\n      }\n      submitAskUserAnswer(sheet, label, { source: \"pick\", values: [label] });\n      return;\n    }\n\n    if (action === \"submit-multi\") {\n      const labels = getSelectedLabels(sheet);\n      if (labels.length === 0) return;\n      submitAskUserAnswer(sheet, labels.join(\", \"), {\n        source: \"multi\",\n        values: labels,\n      });\n      return;\n    }\n\n    if (action === \"open-free-text\") {\n      const row = sheet.querySelector<HTMLElement>('[data-ask-free-text-row=\"true\"]');\n      if (row) {\n        row.classList.remove(\"persona-hidden\");\n        const input = row.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n        input?.focus();\n      }\n      return;\n    }\n\n    if (action === \"focus-free-text\") {\n      // Rows-layout Other row: input lives inside the row container itself.\n      // Native click on the input already focuses it; this branch handles\n      // clicks on the badge or row chrome AND digit-shortcut activations.\n      const input = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n      input?.focus();\n      return;\n    }\n\n    if (action === \"submit-free-text\") {\n      const input = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n      const text = input?.value ?? \"\";\n      if (!text.trim()) return;\n      if (isGroupedSheet(sheet)) {\n        setCurrentAnswer(sheet, text.trim());\n        persistGroupedProgress(sheet);\n        maybeAutoAdvance(sheet);\n        return;\n      }\n      submitAskUserAnswer(sheet, text, { source: \"free-text\" });\n      return;\n    }\n\n    if (action === \"next\" || action === \"back\") {\n      if (!sessionRef.current) return;\n      const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n      const sourceMessage = sessionRef.current\n        .getMessages()\n        .find((m) => m.toolCall?.id === toolCallId);\n      if (!sourceMessage) return;\n      // Flush any unsubmitted free-text input as the current answer.\n      const freeInput = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n      const pending = freeInput?.value?.trim() ?? \"\";\n      if (pending) {\n        const stored = readAnswersFromSheet(sheet)[getCurrentIndex(sheet)];\n        if (typeof stored !== \"string\" || stored !== pending) {\n          setCurrentAnswer(sheet, pending);\n        }\n      }\n      const direction = action === \"next\" ? 1 : -1;\n      const nextIdx = getCurrentIndex(sheet) + direction;\n      navigateToPage(sheet, sourceMessage, config, nextIdx);\n      persistGroupedProgress(sheet);\n      return;\n    }\n\n    if (action === \"submit-all\") {\n      if (!sessionRef.current) return;\n      const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n      const sourceMessage = sessionRef.current\n        .getMessages()\n        .find((m) => m.toolCall?.id === toolCallId);\n      if (!sourceMessage) return;\n      // Flush any pending free-text on the final page first.\n      const freeInput = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n      const pending = freeInput?.value?.trim() ?? \"\";\n      if (pending) setCurrentAnswer(sheet, pending);\n\n      const structured = buildStructuredAnswers(sheet, sourceMessage);\n      // Persist final answers to message metadata BEFORE resolving so the\n      // answered-state review card (which reads `agentMetadata\n      // .askUserQuestionAnswers`) shows the user's actual picks instead of\n      // \"(skipped)\" placeholders. Without this, any answer set only via the\n      // pending-flush above (or via paths that bypassed the per-pick persist\n      // hook) would be missing from the transcript review even though it\n      // landed in the structured payload sent to the agent.\n      sessionRef.current.persistAskUserQuestionProgress(sourceMessage, {\n        answers: structured,\n        currentIndex: getCurrentIndex(sheet),\n      });\n      const summary = stringifyStructured(structured);\n      submitAskUserAnswer(sheet, summary || \"(submitted)\", {\n        source: \"submit-all\",\n        structured,\n      });\n      return;\n    }\n\n    if (action === \"skip\") {\n      if (!sessionRef.current) return;\n      const toolCallId = sheet.getAttribute(\"data-tool-call-id\") ?? \"\";\n      const sourceMessage = sessionRef.current\n        .getMessages()\n        .find((m) => m.toolCall?.id === toolCallId);\n      if (!sourceMessage) return;\n\n      const grouped = isGroupedSheet(sheet);\n      const idx = getCurrentIndex(sheet);\n      const count = getQuestionCount(sheet);\n      const isFinal = idx >= count - 1;\n\n      // Single-question payloads behave like dismiss.\n      if (!grouped) {\n        mount.dispatchEvent(\n          new CustomEvent(\"persona:askUserQuestion:dismissed\", {\n            detail: { toolUseId: toolCallId },\n            bubbles: true,\n            composed: true,\n          })\n        );\n        removeAskUserQuestionSheet(askUserOverlay, toolCallId);\n        if (sourceMessage.agentMetadata?.awaitingLocalTool) {\n          sessionRef.current.markAskUserQuestionResolved(sourceMessage);\n          sessionRef.current.resolveAskUserQuestion(sourceMessage, \"(dismissed)\");\n        }\n        return;\n      }\n\n      // Drop the current question's answer (if any) so it's absent from the\n      // resolved Record. setCurrentAnswer with an empty string deletes the\n      // index from the in-memory map.\n      setCurrentAnswer(sheet, \"\");\n      // Also clear any unsubmitted free-text on this page.\n      const freeInput = sheet.querySelector<HTMLInputElement>('[data-ask-free-text-input=\"true\"]');\n      if (freeInput) freeInput.value = \"\";\n\n      if (isFinal) {\n        // Submit with whatever has been recorded so far.\n        const structured = buildStructuredAnswers(sheet, sourceMessage);\n        const summary = stringifyStructured(structured);\n        submitAskUserAnswer(sheet, summary || \"(skipped)\", {\n          source: \"submit-all\",\n          structured,\n        });\n        return;\n      }\n\n      // Intermediate page: advance one step without recording.\n      navigateToPage(sheet, sourceMessage, config, idx + 1);\n      persistGroupedProgress(sheet);\n      return;\n    }\n  });\n\n  // Enter on the free-text input → submit. Stays on the overlay because the\n  // event target IS the input, which lives inside the overlay subtree.\n  askUserOverlay.addEventListener(\"keydown\", (event) => {\n    if (event.key !== \"Enter\") return;\n    const target = event.target as HTMLElement;\n    const input = target as HTMLInputElement;\n    if (!input.matches?.('[data-ask-free-text-input=\"true\"]')) return;\n    const sheet = input.closest<HTMLElement>(\"[data-persona-ask-sheet-for]\");\n    if (!sheet) return;\n    event.preventDefault();\n    const text = input.value;\n    if (!text.trim()) return;\n    if (isGroupedSheet(sheet)) {\n      setCurrentAnswer(sheet, text.trim());\n      persistGroupedProgress(sheet);\n      maybeAutoAdvance(sheet);\n      return;\n    }\n    submitAskUserAnswer(sheet, text, { source: \"free-text\" });\n  });\n\n  // Digit 1–9 → pick option N on the current rows-layout single-select page.\n  // Listens on `document` so the shortcut fires regardless of where focus\n  // currently sits (host page body, panel chrome, anywhere). The handler\n  // gates strictly: only fires when an active sheet is mounted in our\n  // overlay, and bails when focus is on any input/textarea/contenteditable\n  // (covers the free-text input, the chat composer, and any host-page input).\n  const handleAskUserDigitKey = (event: KeyboardEvent): void => {\n    if (!/^[1-9]$/.test(event.key)) return;\n    if (event.metaKey || event.ctrlKey || event.altKey) return;\n    const target = event.target as HTMLElement | null;\n    if (\n      target?.tagName === \"INPUT\" ||\n      target?.tagName === \"TEXTAREA\" ||\n      target?.isContentEditable\n    ) {\n      return;\n    }\n    const sheet = askUserOverlay.querySelector<HTMLElement>(\"[data-persona-ask-sheet-for]\");\n    if (!sheet) return;\n    if (sheet.getAttribute(\"data-ask-layout\") !== \"rows\") return;\n    if (sheet.getAttribute(\"data-multi-select\") === \"true\") return;\n    const n = Number(event.key);\n    const pills = sheet.querySelectorAll<HTMLElement>(\n      '[data-ask-pill-list=\"true\"] [data-ask-user-action=\"pick\"], [data-ask-pill-list=\"true\"] [data-ask-user-action=\"focus-free-text\"]'\n    );\n    const target_pill = pills[n - 1];\n    if (!target_pill) return;\n    event.preventDefault();\n    target_pill.click();\n  };\n  document.addEventListener(\"keydown\", handleAskUserDigitKey);\n\n  let artifactSplitRoot: HTMLElement | null = null;\n  let artifactResizeHandle: HTMLElement | null = null;\n  let artifactResizeUnbind: (() => void) | null = null;\n  let artifactResizeDocEnd: (() => void) | null = null;\n  let reconcileArtifactResize: () => void = () => {};\n  // Expanded-state transition tracking: stash the resizer's inline width/maxWidth\n  // (which would otherwise beat the expanded class) once per enter, restore once\n  // per leave.\n  let artifactExpandedApplied = false;\n  let artifactStashedWidth = \"\";\n  let artifactStashedMaxWidth = \"\";\n\n  function stopArtifactResizePointer() {\n    artifactResizeDocEnd?.();\n    artifactResizeDocEnd = null;\n  }\n\n  /** Flush split: overlay handle on the seam so it does not consume flex gap (extension + resizable). */\n  const positionExtensionArtifactResizeHandle = () => {\n    if (!artifactSplitRoot || !artifactResizeHandle) return;\n    // Welded splits (panel + seamless) have gap 0, so the handle overlays the seam\n    // instead of consuming flex width; detached keeps a real gap and a flex handle.\n    const ext = mount.classList.contains(\"persona-artifact-welded-split\");\n    const ownerWin = mount.ownerDocument.defaultView ?? window;\n    const mobile = ownerWin.innerWidth <= 640;\n    if (!ext || mount.classList.contains(\"persona-artifact-narrow-host\") || mobile) {\n      artifactResizeHandle.style.removeProperty(\"position\");\n      artifactResizeHandle.style.removeProperty(\"left\");\n      artifactResizeHandle.style.removeProperty(\"top\");\n      artifactResizeHandle.style.removeProperty(\"bottom\");\n      artifactResizeHandle.style.removeProperty(\"width\");\n      artifactResizeHandle.style.removeProperty(\"z-index\");\n      return;\n    }\n    const chat = artifactSplitRoot.firstElementChild as HTMLElement | null;\n    if (!chat || chat === artifactResizeHandle) return;\n    const hitW = 10;\n    artifactResizeHandle.style.position = \"absolute\";\n    artifactResizeHandle.style.top = \"0\";\n    artifactResizeHandle.style.bottom = \"0\";\n    artifactResizeHandle.style.width = `${hitW}px`;\n    artifactResizeHandle.style.zIndex = \"5\";\n    // Center the overlay on the seam: with an explicit welded gap the seam sits\n    // half a gap past the chat's right edge (welded default gap is 0).\n    const gapPx = readFlexGapPx(artifactSplitRoot, ownerWin);\n    const left = chat.offsetWidth + gapPx / 2 - hitW / 2;\n    artifactResizeHandle.style.left = `${Math.max(0, left)}px`;\n  };\n\n  /** No-op until artifact pane is created; replaced below when artifacts are enabled. */\n  let applyLauncherArtifactPanelWidth: () => void = () => {};\n\n  // Mobile fullscreen renders a flush, chrome-less panel, so the split chrome\n  // must stand down there. This is the union of the two fullscreen predicates\n  // applyFullHeightStyles/recalcPanelHeight use: launcher fullscreen\n  // (shouldGoFullscreen) and the docked-host variant (dockedHostFullscreen).\n  // Unlike the 641px split breakpoint, it honors the configurable mobileBreakpoint.\n  const isMobileFullscreenActive = (): boolean => {\n    const win = mount.ownerDocument.defaultView ?? window;\n    const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;\n    const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;\n    if (!mobileFullscreen || win.innerWidth > mobileBreakpoint) return false;\n    return launcherEnabled || isDockedMountMode(config);\n  };\n\n  // Desktop side-by-side split chrome mode. 'welded' = panel|seamless (one card,\n  // border+radius wrap both columns); 'detached' = two floating cards; 'none' when\n  // the pane is closed, the narrow-host drawer, or mobile fullscreen.\n  const splitChromeMode = (): \"none\" | \"welded\" | \"detached\" => {\n    if (!artifactsSidebarEnabled(config)) return \"none\";\n    if (!artifactPaneVisible()) return \"none\";\n    if (mount.classList.contains(\"persona-artifact-narrow-host\")) return \"none\";\n    // A fullscreen panel is flush; welded/detached corner chrome would fight it.\n    if (isMobileFullscreenActive()) return \"none\";\n    const win = mount.ownerDocument.defaultView ?? window;\n    // Must match the artifact split media queries in widget.css (drawer at\n    // max-width:640, split at min-width:641); mobileBreakpoint does not move it.\n    if (win.innerWidth < ARTIFACT_SPLIT_DESKTOP_MIN) return \"none\";\n    return isArtifactPaneAppearanceDetached(config) ? \"detached\" : \"welded\";\n  };\n\n  const syncArtifactPane = () => {\n    if (!artifactPaneApi || !artifactsSidebarEnabled(config)) return;\n    applyArtifactLayoutCssVars(mount, config);\n    applyArtifactPaneAppearance(mount, config);\n    applyLauncherArtifactPanelWidth();\n    const threshold = config.features?.artifacts?.layout?.narrowHostMaxWidth ?? 520;\n    const w = panel.getBoundingClientRect().width || 0;\n    mount.classList.toggle(\"persona-artifact-narrow-host\", w > 0 && w <= threshold);\n    // Thread the single source of truth for \"pane surface is shown\" into the\n    // pane BEFORE update() so it renders lazily: in inline/card display modes\n    // the pane stays hidden and must not build a second sandboxed artifact\n    // iframe (which would execute artifact scripts twice). The pane records\n    // state while hidden and renders on the next reveal. Set first so an\n    // update() that arrives while hidden is skipped, not rendered-then-hidden.\n    const paneVisible = artifactPaneVisible();\n    artifactPaneApi.setVisible(paneVisible);\n    artifactPaneApi.update(lastArtifactsState);\n    if (artifactsPaneUserHidden) {\n      artifactPaneApi.setMobileOpen(false);\n      artifactPaneApi.element.classList.add(\"persona-hidden\");\n      artifactPaneApi.backdrop?.classList.add(\"persona-hidden\");\n      artifactPaneExpanded = false;\n      artifactPaneExpandedPinned = false;\n    } else if (lastArtifactsState.artifacts.length > 0 && artifactPaneCanShow()) {\n      // User chose “show” again (e.g. programmatic showArtifacts): clear dismiss chrome\n      // and force drawer open so narrow-host / mobile slide-out is not stuck off-screen.\n      // Artifacts whose display mode is \"card\" or \"inline\" don't auto-open the\n      // pane (artifactPaneCanShow); it stays hidden until an explicit open or\n      // until a \"panel\"-mode artifact arrives.\n      artifactPaneApi.element.classList.remove(\"persona-hidden\");\n      artifactPaneApi.setMobileOpen(true);\n    } else {\n      // The pane's own update() unhides itself whenever records exist\n      // (applyLayoutVisibility), so re-assert the gate here: card/inline-mode\n      // artifacts keep the pane hidden until an explicit open.\n      artifactPaneApi.setMobileOpen(false);\n      artifactPaneApi.element.classList.add(\"persona-hidden\");\n      artifactPaneApi.backdrop?.classList.add(\"persona-hidden\");\n      artifactPaneExpanded = false;\n      artifactPaneExpandedPinned = false;\n    }\n    // Re-read the toggle config on every sync so a live config.update() can\n    // reveal/remove the button (the pane itself is built once). Disabling the\n    // toggle while expanded also collapses the pane — unless the expansion is\n    // pinned (inline Expand): that fullscreen request stands on its own and\n    // exits via Close.\n    const expandToggleEnabled = config.features?.artifacts?.layout?.showExpandToggle === true;\n    artifactPaneApi.setExpandToggleVisible(expandToggleEnabled);\n    artifactPaneApi.setCopyButtonVisible(\n      config.features?.artifacts?.layout?.showCopyButton === true\n    );\n    artifactPaneApi.setCustomActions(config.features?.artifacts?.toolbarActions ?? []);\n    artifactPaneApi.setTabFade(config.features?.artifacts?.layout?.tabFade);\n    artifactPaneApi.setRenderTabBar(config.features?.artifacts?.renderTabBar);\n    if (!expandToggleEnabled && !artifactPaneExpandedPinned) artifactPaneExpanded = false;\n    // Run the resizer stash/restore once per expanded-state transition: the\n    // resizer's inline width/maxWidth beats the expanded class, so clear it while\n    // expanded and put it back on collapse.\n    if (artifactPaneExpanded !== artifactExpandedApplied) {\n      const paneEl = artifactPaneApi.element;\n      if (artifactPaneExpanded) {\n        artifactStashedWidth = paneEl.style.width;\n        artifactStashedMaxWidth = paneEl.style.maxWidth;\n        paneEl.style.removeProperty(\"width\");\n        paneEl.style.removeProperty(\"max-width\");\n      } else {\n        if (artifactStashedWidth) paneEl.style.width = artifactStashedWidth;\n        if (artifactStashedMaxWidth) paneEl.style.maxWidth = artifactStashedMaxWidth;\n        artifactStashedWidth = \"\";\n        artifactStashedMaxWidth = \"\";\n      }\n      artifactExpandedApplied = artifactPaneExpanded;\n    }\n    mount.classList.toggle(\"persona-artifact-expanded\", artifactPaneExpanded);\n    artifactPaneApi.setExpanded(artifactPaneExpanded);\n    // Recompute panel chrome when the split mode changes (pane open/close or\n    // appearance) so the welded border and detached shadow suppression land.\n    const modeNow = splitChromeMode();\n    if (modeNow !== appliedSplitMode) {\n      appliedSplitMode = modeNow;\n      syncPanelChrome();\n    }\n    reconcileArtifactResize();\n  };\n\n  if (artifactsSidebarEnabled(config)) {\n    panel.style.position = \"relative\";\n    const chatColumn = createElement(\n      \"div\",\n      \"persona-flex persona-flex-1 persona-flex-col persona-min-w-0 persona-min-h-0\"\n    );\n    const splitRoot = createElement(\n      \"div\",\n      \"persona-flex persona-h-full persona-w-full persona-min-h-0 persona-artifact-split-root\"\n    );\n    chatColumn.appendChild(container);\n    artifactPaneApi = createArtifactPane(config, {\n      onSelect: (id) => sessionRef.current?.selectArtifact(id),\n      onDismiss: () => {\n        artifactsPaneUserHidden = true;\n        syncArtifactPane();\n      },\n      onToggleExpand: () => {\n        const next = !artifactPaneExpanded;\n        const artifactId =\n          lastArtifactsState.selectedId ??\n          lastArtifactsState.artifacts[lastArtifactsState.artifacts.length - 1]?.id ??\n          null;\n        const prevented = config.features?.artifacts?.onArtifactAction?.({\n          type: \"expand\",\n          artifactId,\n          expanded: next,\n        });\n        if (prevented === true) return;\n        artifactPaneExpanded = next;\n        if (!next) artifactPaneExpandedPinned = false;\n        syncArtifactPane();\n      }\n    });\n    artifactPaneApi.element.classList.add(\"persona-hidden\");\n    artifactSplitRoot = splitRoot;\n    splitRoot.appendChild(chatColumn);\n    splitRoot.appendChild(artifactPaneApi.element);\n    if (artifactPaneApi.backdrop) {\n      panel.appendChild(artifactPaneApi.backdrop);\n    }\n    panel.appendChild(splitRoot);\n\n    reconcileArtifactResize = () => {\n      if (!artifactSplitRoot || !artifactPaneApi) return;\n      const want = config.features?.artifacts?.layout?.resizable === true;\n      if (!want) {\n        artifactResizeUnbind?.();\n        artifactResizeUnbind = null;\n        stopArtifactResizePointer();\n        if (artifactResizeHandle) {\n          artifactResizeHandle.remove();\n          artifactResizeHandle = null;\n        }\n        artifactPaneApi.element.style.removeProperty(\"width\");\n        artifactPaneApi.element.style.removeProperty(\"maxWidth\");\n        return;\n      }\n      if (!artifactResizeHandle) {\n        const handle = createElement(\n          \"div\",\n          \"persona-artifact-split-handle persona-shrink-0 persona-h-full\"\n        );\n        handle.setAttribute(\"role\", \"separator\");\n        handle.setAttribute(\"aria-orientation\", \"vertical\");\n        handle.setAttribute(\"aria-label\", \"Resize artifacts panel\");\n        handle.tabIndex = 0;\n\n        const doc = mount.ownerDocument;\n        const win = doc.defaultView ?? window;\n\n        const onPointerDown = (e: PointerEvent) => {\n          if (!artifactPaneApi || e.button !== 0) return;\n          if (mount.classList.contains(\"persona-artifact-narrow-host\")) return;\n          if (mount.classList.contains(\"persona-artifact-expanded\")) return;\n          if (win.innerWidth <= 640) return;\n          e.preventDefault();\n          stopArtifactResizePointer();\n          const startX = e.clientX;\n          const startW = artifactPaneApi.element.getBoundingClientRect().width;\n          const layout = config.features?.artifacts?.layout;\n          const onMove = (ev: PointerEvent) => {\n            const splitW = artifactSplitRoot!.getBoundingClientRect().width;\n            const weldedChrome = mount.classList.contains(\"persona-artifact-welded-split\");\n            // Read the effective flex gap either way: welded defaults to 0 but an\n            // explicit splitGap opens a real gap that still consumes row width.\n            const gapPx = readFlexGapPx(artifactSplitRoot!, win);\n            // Welded overlays the handle on the seam (out of flow), so it consumes\n            // no width; detached keeps it as an in-flow flex child.\n            const handleW = weldedChrome ? 0 : handle.getBoundingClientRect().width || 6;\n            // Handle is left of the artifact: drag left widens artifact, drag right narrows it.\n            const next = startW - (ev.clientX - startX);\n            const clamped = resolveArtifactPaneWidthPx(\n              next,\n              splitW,\n              gapPx,\n              handleW,\n              layout?.resizableMinWidth,\n              layout?.resizableMaxWidth\n            );\n            artifactPaneApi!.element.style.width = `${clamped}px`;\n            artifactPaneApi!.element.style.maxWidth = \"none\";\n            positionExtensionArtifactResizeHandle();\n          };\n          const onUp = () => {\n            doc.removeEventListener(\"pointermove\", onMove);\n            doc.removeEventListener(\"pointerup\", onUp);\n            doc.removeEventListener(\"pointercancel\", onUp);\n            artifactResizeDocEnd = null;\n            try {\n              handle.releasePointerCapture(e.pointerId);\n            } catch {\n              /* ignore */\n            }\n          };\n          artifactResizeDocEnd = onUp;\n          doc.addEventListener(\"pointermove\", onMove);\n          doc.addEventListener(\"pointerup\", onUp);\n          doc.addEventListener(\"pointercancel\", onUp);\n          try {\n            handle.setPointerCapture(e.pointerId);\n          } catch {\n            /* ignore */\n          }\n        };\n\n        handle.addEventListener(\"pointerdown\", onPointerDown);\n        artifactResizeHandle = handle;\n        artifactSplitRoot.insertBefore(handle, artifactPaneApi.element);\n        artifactResizeUnbind = () => {\n          handle.removeEventListener(\"pointerdown\", onPointerDown);\n        };\n      }\n      if (artifactResizeHandle) {\n        const has = artifactPaneVisible();\n        artifactResizeHandle.classList.toggle(\"persona-hidden\", !has);\n        positionExtensionArtifactResizeHandle();\n      }\n    };\n\n    applyLauncherArtifactPanelWidth = () => {\n      if (!launcherEnabled || !artifactPaneApi) return;\n      const sidebarMode = config.launcher?.sidebarMode ?? false;\n      if (sidebarMode) return;\n      if (isDockedMountMode(config) && resolveDockConfig(config).reveal === \"emerge\") return;\n      const ownerWindow = mount.ownerDocument.defaultView ?? window;\n      const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;\n      const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;\n      if (mobileFullscreen && ownerWindow.innerWidth <= mobileBreakpoint) return;\n      if (!shouldExpandLauncherForArtifacts(config, launcherEnabled)) return;\n\n      const base = config.launcher?.width ?? config.launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;\n      const expanded =\n        config.features?.artifacts?.layout?.expandedPanelWidth ??\n        \"min(720px, calc(100vw - 24px))\";\n      const hasVisible = artifactPaneVisible();\n      if (hasVisible) {\n        panel.style.width = expanded;\n        panel.style.maxWidth = expanded;\n      } else {\n        panel.style.width = base;\n        panel.style.maxWidth = base;\n      }\n    };\n\n    if (typeof ResizeObserver !== \"undefined\") {\n      artifactPanelResizeObs = new ResizeObserver(() => {\n        syncArtifactPane();\n      });\n      artifactPanelResizeObs.observe(panel);\n    }\n  } else {\n    panel.appendChild(container);\n  }\n\n  // Composer-bar mode: the pill (footer) and peek banner live in a\n  // viewport-fixed sibling of the wrapper (`pillRoot`) so they're\n  // independent of both the wrapper's geometry transitions and the panel's\n  // optional artifact split layout. Critical for modal mode: the wrapper\n  // there has `transform: translate(-50%, -50%)`, which would establish a\n  // containing block trapping any `position: fixed` descendant.\n  //\n  // Order inside pillRoot: peekBanner (slim row above pill) → footer (pill).\n  // pillRoot's `gap` spaces them; the peek is hidden by default until ui.ts\n  // toggles `.persona-pill-peek--visible` based on streaming/hover/open state.\n  if (isComposerBar() && pillRoot) {\n    if (panelElements.peekBanner) {\n      pillRoot.appendChild(panelElements.peekBanner);\n    }\n    pillRoot.appendChild(footer);\n  }\n  mount.appendChild(wrapper);\n  // pillRoot is mounted *after* wrapper so it naturally stacks on top\n  // when both share the same z-index (e.g. fullscreen mode where the\n  // pill should float above the chat panel chrome).\n  if (pillRoot) {\n    mount.appendChild(pillRoot);\n  }\n\n  // Apply full-height and sidebar styles if enabled\n  // This ensures the widget fills its container height with proper flex layout\n  const applyFullHeightStyles = () => {\n    // Composer-bar mode owns its own sizing/chrome. Geometry comes from\n    // `applyComposerBarGeometry()` (per-state inline on the wrapper), the\n    // pill carries its own chrome via `.persona-pill-composer`, and the\n    // expanded chat panel chrome (border + radius + shadow + bg) is painted\n    // inline on the `container` (NOT the panel: the panel is a transparent\n    // flex column with a gap so the pill renders as a sibling below the\n    // chrome). Same theme contract as floating mode\n    // (`theme.components.panel.{shadow,border,borderRadius}`); collapsed\n    // clears it (container is hidden via display:none anyway), expanded\n    // re-applies it, with the `fullscreen` variant intentionally chrome-less.\n    if (isComposerBar()) {\n      panel.style.width = \"100%\";\n      panel.style.maxWidth = \"100%\";\n      const cb = config.launcher?.composerBar ?? {};\n      const isExpanded = wrapper.dataset.state === \"expanded\";\n      const expandedSize = cb.expandedSize ?? \"anchored\";\n      const wantsChrome = isExpanded && expandedSize !== \"fullscreen\";\n      if (!wantsChrome) {\n        container.style.background = \"\";\n        container.style.border = \"\";\n        container.style.borderRadius = \"\";\n        container.style.overflow = \"\";\n        container.style.boxShadow = \"\";\n        return;\n      }\n      const panelPartial = config.theme?.components?.panel;\n      const activeTheme = getActiveTheme(config);\n      const resolveCb = (raw: string | undefined, fallback: string): string => {\n        if (raw == null || raw === \"\") return fallback;\n        return resolveTokenValue(activeTheme, raw) ?? raw;\n      };\n      const defaultBorder = \"1px solid var(--persona-border)\";\n      const defaultShadow = \"var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25))\";\n      const defaultRadius = \"var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))\";\n      container.style.background = \"var(--persona-surface, #ffffff)\";\n      container.style.border = resolveCb(panelPartial?.border, defaultBorder);\n      container.style.borderRadius = resolveCb(panelPartial?.borderRadius, defaultRadius);\n      container.style.boxShadow = resolveCb(panelPartial?.shadow, defaultShadow);\n      container.style.overflow = \"hidden\";\n      return;\n    }\n    const dockedMode = isDockedMountMode(config);\n    const sidebarMode = config.launcher?.sidebarMode ?? false;\n    const fullHeight = dockedMode || sidebarMode || (config.launcher?.fullHeight ?? false);\n    /** Script-tag / div embed: launcher off, host supplies a sized mount. */\n    const isInlineEmbed = config.launcher?.enabled === false;\n    /** Detached appearance: inset card over a canvas instead of flush chrome. */\n    const isDetached = config.launcher?.detachedPanel === true;\n    const panelPartial = config.theme?.components?.panel;\n    const activeTheme = getActiveTheme(config);\n    const resolvePanelChrome = (raw: string | undefined, fallback: string): string => {\n      if (raw == null || raw === \"\") return fallback;\n      return resolveTokenValue(activeTheme, raw) ?? raw;\n    };\n\n    // Mobile fullscreen detection\n    // Use mount's ownerDocument window to get correct viewport width when widget is inside an iframe\n    const ownerWindow = mount.ownerDocument.defaultView ?? window;\n    const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;\n    const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;\n    const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;\n    const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;\n    // Docked host layout forces fullscreen on mobile even when the launcher is\n    // off (host-layout pins the slot fixed and clears its chrome). Suppress the\n    // detached card so it never paints chrome over a flush fullscreen panel.\n    const dockedHostFullscreen = dockedMode && mobileFullscreen && isMobileViewport;\n\n    // Determine panel styling based on mode, with theme overrides\n    const position = config.launcher?.position ?? 'bottom-left';\n    const isLeftSidebar = position === 'bottom-left' || position === 'top-left';\n    const overlayZIndex = config.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX;\n\n    // Card chrome defaults (floating look): reused to restore detached chrome.\n    // Defaults chain through the aliases themeToCssVariables emits so explicit\n    // theme.components.panel overrides and these defaults never diverge.\n    const cardBorder = 'var(--persona-panel-border, 1px solid var(--persona-border))';\n    const cardShadow = 'var(--persona-panel-shadow, var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25)))';\n    const cardRadius = 'var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))';\n    /** Detached restores card chrome except when a host layout goes fullscreen. */\n    const detachedCard = isDetached && !shouldGoFullscreen && !dockedHostFullscreen;\n    // Stamp reflects rendered chrome: cleared when a fullscreen host layout\n    // suppresses the card, so the attribute never lies to the detached CSS.\n    if (detachedCard) {\n      mount.setAttribute(\"data-persona-panel-detached\", \"true\");\n    } else {\n      mount.removeAttribute(\"data-persona-panel-detached\");\n    }\n\n    // Default values based on mode\n    let defaultPanelBorder = detachedCard\n      ? cardBorder\n      : (sidebarMode || shouldGoFullscreen) ? 'none' : cardBorder;\n    let defaultPanelShadow = detachedCard\n      ? cardShadow\n      : shouldGoFullscreen\n        ? 'none'\n        : sidebarMode\n          ? (isLeftSidebar ? 'var(--persona-palette-shadows-sidebar-left, 2px 0 12px rgba(0, 0, 0, 0.08))' : 'var(--persona-palette-shadows-sidebar-right, -2px 0 12px rgba(0, 0, 0, 0.08))')\n          // Flush inline embeds fill their container: no elevation by default\n          // (detachedPanel or components.panel.shadow opts back in).\n          : isInlineEmbed ? 'none' : cardShadow;\n\n    if (dockedMode && !shouldGoFullscreen && !detachedCard) {\n      defaultPanelShadow = 'none';\n      defaultPanelBorder = 'none';\n    }\n    const defaultPanelBorderRadius = detachedCard\n      ? cardRadius\n      : (sidebarMode || shouldGoFullscreen) ? '0' : cardRadius;\n\n    // Apply theme overrides or defaults (components.panel.*)\n    const panelBorder = resolvePanelChrome(panelPartial?.border, defaultPanelBorder);\n    const panelShadow = resolvePanelChrome(panelPartial?.shadow, defaultPanelShadow);\n    const panelBorderRadius = resolvePanelChrome(panelPartial?.borderRadius, defaultPanelBorderRadius);\n\n    // Split chrome: 'welded' folds the card border onto the outer panel so it\n    // wraps both columns as one card (shadow/radius already on the panel);\n    // 'detached' pulls them into two individually carded columns.\n    const splitMode = splitChromeMode();\n    const detachedSplitActive = splitMode === \"detached\";\n    const weldedSplitActive = splitMode === \"welded\";\n    mount.classList.toggle(\"persona-artifact-detached-split\", detachedSplitActive);\n    mount.classList.toggle(\"persona-artifact-welded-split\", weldedSplitActive);\n    const chatCardShadow =\n      'var(--persona-artifact-chat-shadow, var(--persona-artifact-pane-shadow, var(--persona-panel-shadow, var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25)))))';\n    // Welded moves the chat card border onto the panel, so the chat column goes\n    // borderless; detached cards it individually; otherwise it keeps the border.\n    // 'flush' drops the chat card so the chat is flat flush background and only\n    // the pane floats; 'card' (default) keeps two matched cards. Flush is a\n    // steady state: it applies whether or not a pane is open, so opening or\n    // closing an artifact never flips the chat chrome. Gated to inline embeds\n    // (not docked), matching the card-mode perimeter inset: the flush model only\n    // fits the container-filling embed. Floating/docked/sidebar keep the card look.\n    const rawChatSurface = config.features?.artifacts?.layout?.chatSurface;\n    const resolvedChatSurface = rawChatSurface === \"flush\" ? \"flush\" : \"card\";\n    const chatFlush =\n      resolvedChatSurface === \"flush\" &&\n      artifactsSidebarEnabled(config) &&\n      isArtifactPaneAppearanceDetached(config) &&\n      isInlineEmbed &&\n      !dockedMode;\n    mount.classList.toggle(\"persona-artifact-chat-flush\", chatFlush);\n    // Flat means flat: flush suppresses the outer panel shadow in every state.\n    const outerPanelShadow = detachedSplitActive || chatFlush ? 'none' : panelShadow;\n    let chatContainerBorder = detachedSplitActive\n      ? cardBorder\n      : weldedSplitActive\n        ? 'none'\n        : panelBorder;\n    let chatContainerRadius = detachedSplitActive ? cardRadius : panelBorderRadius;\n    if (chatFlush) {\n      chatContainerBorder = 'none';\n      chatContainerRadius = '0';\n    }\n    // Flush fills the container flush, so the outer panel squares off by default;\n    // the pane keeps its own rounded radius. An explicit panel.borderRadius wins.\n    const panelRadiusExplicit =\n      panelPartial?.borderRadius != null && panelPartial.borderRadius !== '';\n    const appliedPanelRadius =\n      chatFlush && !panelRadiusExplicit ? '0' : panelBorderRadius;\n\n    // Clearing body.style.cssText below wipes the inline `flex: 1 1 0%` /\n    // `min-height: 0` / `overflow-y: auto` that make the messages area a\n    // scroll container. Between the reset and the mode-specific reapply,\n    // the body's clientHeight == scrollHeight momentarily, so the browser\n    // clamps scrollTop to 0, and a synchronous restore at the end of this\n    // function runs before layout has reflowed, so the write is also\n    // clamped. Defer the restore to the next frame, once the reapplied\n    // styles have produced a scrollable container again.\n    const prevBodyScrollTop = body.scrollTop;\n\n    // Reset all inline styles first to handle mode toggling\n    // This ensures styles don't persist when switching between modes\n    mount.style.cssText = '';\n    wrapper.style.cssText = '';\n    panel.style.cssText = '';\n    container.style.cssText = '';\n    body.style.cssText = '';\n    footer.style.cssText = '';\n\n    // Preserve the event-stream takeover across a layout-mode change. The\n    // cssText reset above wiped the `display: none` that toggleEventStreamOn\n    // set on the messages body, and none of the per-mode reapply branches below\n    // touch `display` — so without this the messages would reappear and stack\n    // above the event panel when the window crosses the fullscreen breakpoint.\n    if (eventStreamVisible) {\n      body.style.display = \"none\";\n    }\n\n    const restoreBodyScrollTop = (): void => {\n      if (prevBodyScrollTop <= 0) return;\n      const ownerWindow = body.ownerDocument.defaultView ?? window;\n      ownerWindow.requestAnimationFrame(() => {\n        if (body.scrollTop === prevBodyScrollTop) return;\n        // If scrollHeight collapsed (content actually shrank), don't fight it\n        const maxScrollTop = body.scrollHeight - body.clientHeight;\n        if (maxScrollTop <= 0) return;\n        body.scrollTop = Math.min(prevBodyScrollTop, maxScrollTop);\n      });\n    };\n    \n    // Mobile fullscreen: fill entire viewport with no radius/shadow/margins\n    if (shouldGoFullscreen) {\n      // Remove position offset classes\n      wrapper.classList.remove(\n        'persona-bottom-6', 'persona-right-6', 'persona-left-6', 'persona-top-6',\n        'persona-bottom-4', 'persona-right-4', 'persona-left-4', 'persona-top-4'\n      );\n\n      // Wrapper: fill entire viewport\n      wrapper.style.cssText = `\n        position: fixed !important;\n        inset: 0 !important;\n        width: 100% !important;\n        height: 100% !important;\n        max-height: 100% !important;\n        margin: 0 !important;\n        padding: 0 !important;\n        display: flex !important;\n        flex-direction: column !important;\n        z-index: ${overlayZIndex} !important;\n        background-color: var(--persona-surface, #ffffff) !important;\n      `;\n\n      // Panel: fill wrapper, no radius/shadow\n      panel.style.cssText = `\n        position: relative !important;\n        display: flex !important;\n        flex-direction: column !important;\n        flex: 1 1 0% !important;\n        width: 100% !important;\n        max-width: 100% !important;\n        height: 100% !important;\n        min-height: 0 !important;\n        margin: 0 !important;\n        padding: 0 !important;\n        box-shadow: none !important;\n        border-radius: 0 !important;\n      `;\n\n      // Container: fill panel, no radius/border\n      container.style.cssText = `\n        display: flex !important;\n        flex-direction: column !important;\n        flex: 1 1 0% !important;\n        width: 100% !important;\n        height: 100% !important;\n        min-height: 0 !important;\n        max-height: 100% !important;\n        overflow: hidden !important;\n        border-radius: 0 !important;\n        border: none !important;\n      `;\n\n      // Body: scrollable messages\n      body.style.flex = '1 1 0%';\n      body.style.minHeight = '0';\n      body.style.overflowY = 'auto';\n\n      // Footer: pinned at bottom\n      footer.style.flexShrink = '0';\n\n      wasMobileFullscreen = true;\n      restoreBodyScrollTop();\n      return; // Skip remaining mode logic\n    }\n\n    // Re-apply panel width/maxWidth from initial setup\n    const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;\n    const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;\n    if (!sidebarMode && !dockedMode) {\n      if (isInlineEmbed && fullHeight) {\n        panel.style.width = \"100%\";\n        panel.style.maxWidth = \"100%\";\n      } else {\n        panel.style.width = width;\n        panel.style.maxWidth = width;\n      }\n    } else if (dockedMode) {\n      const dockReveal = resolveDockConfig(config).reveal;\n      if (dockReveal === \"emerge\" && !isDetached) {\n        const dw = resolveDockConfig(config).width;\n        panel.style.width = dw;\n        panel.style.maxWidth = dw;\n      } else {\n        panel.style.width = \"100%\";\n        panel.style.maxWidth = \"100%\";\n      }\n    }\n    applyLauncherArtifactPanelWidth();\n\n    // Apply panel styling\n    // Box-shadow is applied to panel (parent) instead of container to avoid\n    // rendering artifacts when container has overflow:hidden + border-radius\n    // Panel also gets border-radius to make the shadow follow the rounded corners\n    panel.style.boxShadow = outerPanelShadow;\n    panel.style.borderRadius = appliedPanelRadius;\n    if (detachedSplitActive) panel.style.border = 'none';\n    else if (weldedSplitActive) panel.style.border = panelBorder;\n    container.style.border = chatContainerBorder;\n    container.style.borderRadius = chatContainerRadius;\n    container.style.boxShadow = detachedSplitActive && !chatFlush ? chatCardShadow : '';\n    // Flush chat has no card surface of its own: override the class-painted\n    // container/body/footer backgrounds so the wrapper's canvas shows through.\n    // The footer's top hairline is chat-card chrome, so it goes too. Element\n    // surfaces (bubbles, cards, composer input) keep their own backgrounds.\n    if (chatFlush) {\n      container.style.background = 'transparent';\n      body.style.background = 'transparent';\n      footer.style.background = 'transparent';\n      footer.style.borderTop = 'none';\n    }\n\n    // Welded outer-right radius: derive the pane's outer corners from the same\n    // resolved value as the chat card's left corners so a custom\n    // components.panel.borderRadius stays symmetric. Explicit\n    // unifiedSplitOuterRadius / paneBorderRadius still win. Applied in\n    // syncPanelChrome after applyArtifactPaneAppearance (which no longer owns it).\n    if (weldedSplitActive) {\n      const wl = config.features?.artifacts?.layout;\n      weldedOuterRadius =\n        wl?.unifiedSplitOuterRadius?.trim() || wl?.paneBorderRadius?.trim() || panelBorderRadius;\n    } else {\n      weldedOuterRadius = null;\n    }\n\n    if (dockedMode && !shouldGoFullscreen && !detachedCard && !detachedSplitActive && !weldedSplitActive && panelPartial?.border === undefined) {\n      container.style.border = 'none';\n      const dockSide = resolveDockConfig(config).side;\n      if (dockSide === 'right') {\n        container.style.borderLeft = '1px solid var(--persona-border)';\n      } else {\n        container.style.borderRight = '1px solid var(--persona-border)';\n      }\n    }\n\n    // Docked + welded: the welded chrome owner is the outer panel, and docked\n    // mode resolves its border to none, so add the dock-facing hairline there so\n    // the split still separates from the host page. Mirrors the flush block's\n    // side choice (right dock => left edge faces the page).\n    if (dockedMode && !shouldGoFullscreen && weldedSplitActive && panelPartial?.border === undefined) {\n      const dockSide = resolveDockConfig(config).side;\n      if (dockSide === 'right') {\n        panel.style.borderLeft = '1px solid var(--persona-border)';\n      } else {\n        panel.style.borderRight = '1px solid var(--persona-border)';\n      }\n    }\n\n    if (fullHeight) {\n      // Mount container\n      mount.style.display = 'flex';\n      mount.style.flexDirection = 'column';\n      mount.style.height = '100%';\n      mount.style.minHeight = '0';\n      if (isInlineEmbed) {\n        mount.style.width = '100%';\n      }\n      \n      // Wrapper\n      // - Inline embed: needs overflow:hidden to contain the flex layout\n      // - Launcher mode: no overflow:hidden to allow panel's box-shadow to render fully\n      wrapper.style.display = 'flex';\n      wrapper.style.flexDirection = 'column';\n      wrapper.style.flex = '1 1 0%';\n      wrapper.style.minHeight = '0';\n      wrapper.style.maxHeight = '100%';\n      wrapper.style.height = '100%';\n      if (isInlineEmbed) {\n        wrapper.style.overflow = 'hidden';\n      }\n      \n      // Panel\n      panel.style.display = 'flex';\n      panel.style.flexDirection = 'column';\n      panel.style.flex = '1 1 0%';\n      panel.style.minHeight = '0';\n      panel.style.maxHeight = '100%';\n      panel.style.height = '100%';\n      // Detached split: each card clips its own content and carries its own\n      // shadow, so panel overflow:hidden must not clip the cards' shadows.\n      if (!detachedSplitActive) panel.style.overflow = 'hidden';\n\n      // Main container\n      container.style.display = 'flex';\n      container.style.flexDirection = 'column';\n      container.style.flex = '1 1 0%';\n      container.style.minHeight = '0';\n      container.style.maxHeight = '100%';\n      container.style.overflow = 'hidden';\n      \n      // Body (scrollable messages area)\n      body.style.flex = '1 1 0%';\n      body.style.minHeight = '0';\n      body.style.overflowY = 'auto';\n      \n      // Footer (composer) - should not shrink\n      footer.style.flexShrink = '0';\n    }\n\n    // Inline embed: pad the wrapper so the canvas shows around the card when the\n    // panel is a detached card OR a detached split is open (insets the whole split\n    // from the container edges on all sides). Docked owns its inset via\n    // host-layout, so exclude it (no double gap).\n    // Flush chat must be flush to the container, so it skips the perimeter inset;\n    // the wrapper still paints the canvas so one token colors the whole backdrop,\n    // in every state (flush is steady, so idle must match pane-open).\n    if (isInlineEmbed && (detachedCard || detachedSplitActive || chatFlush) && !dockedMode) {\n      if (!chatFlush) wrapper.style.padding = 'var(--persona-panel-inset)';\n      wrapper.style.background = 'var(--persona-panel-canvas-bg)';\n    }\n\n    // Handle positioning classes based on mode\n    // First remove all position classes to reset state\n    wrapper.classList.remove(\n      'persona-bottom-6', 'persona-right-6', 'persona-left-6', 'persona-top-6',\n      'persona-bottom-4', 'persona-right-4', 'persona-left-4', 'persona-top-4'\n    );\n    \n    if (!sidebarMode && !isInlineEmbed && !dockedMode) {\n      // Restore positioning classes when not in sidebar mode (launcher mode only)\n      const positionClasses = positionMap[position as keyof typeof positionMap] ?? positionMap['bottom-right'];\n      positionClasses.split(' ').forEach(cls => wrapper.classList.add(cls));\n    }\n    \n    // Apply sidebar-specific styles\n    if (sidebarMode) {\n      const sidebarWidth = config.launcher?.sidebarWidth ?? '420px';\n\n      // Wrapper - fixed position. Detached insets the card off the edges and\n      // shrinks its height by the inset on both ends; flush hugs the edges.\n      if (isDetached) {\n        wrapper.style.cssText = `\n          position: fixed !important;\n          top: var(--persona-panel-inset) !important;\n          bottom: var(--persona-panel-inset) !important;\n          width: ${sidebarWidth} !important;\n          height: calc(100vh - (2 * var(--persona-panel-inset))) !important;\n          max-height: calc(100vh - (2 * var(--persona-panel-inset))) !important;\n          margin: 0 !important;\n          padding: 0 !important;\n          display: flex !important;\n          flex-direction: column !important;\n          z-index: ${overlayZIndex} !important;\n          ${isLeftSidebar ? 'left: var(--persona-panel-inset) !important; right: auto !important;' : 'left: auto !important; right: var(--persona-panel-inset) !important;'}\n        `;\n      } else {\n        wrapper.style.cssText = `\n          position: fixed !important;\n          top: 0 !important;\n          bottom: 0 !important;\n          width: ${sidebarWidth} !important;\n          height: 100vh !important;\n          max-height: 100vh !important;\n          margin: 0 !important;\n          padding: 0 !important;\n          display: flex !important;\n          flex-direction: column !important;\n          z-index: ${overlayZIndex} !important;\n          ${isLeftSidebar ? 'left: 0 !important; right: auto !important;' : 'left: auto !important; right: 0 !important;'}\n        `;\n      }\n      \n      // Panel - fill wrapper (override inline width/max-width from panel.ts)\n      // Box-shadow is on panel to avoid rendering artifacts with container's overflow:hidden\n      // Border-radius on panel ensures shadow follows rounded corners\n      panel.style.cssText = `\n        position: relative !important;\n        display: flex !important;\n        flex-direction: column !important;\n        flex: 1 1 0% !important;\n        width: 100% !important;\n        max-width: 100% !important;\n        height: 100% !important;\n        min-height: 0 !important;\n        margin: 0 !important;\n        padding: 0 !important;\n        box-shadow: ${outerPanelShadow} !important;\n        border-radius: ${appliedPanelRadius} !important;\n        ${detachedSplitActive ? 'border: none !important;' : weldedSplitActive ? `border: ${panelBorder} !important;` : ''}\n      `;\n      // Force override any inline width/maxWidth that may be set elsewhere\n      panel.style.setProperty('width', '100%', 'important');\n      panel.style.setProperty('max-width', '100%', 'important');\n\n      // Container - apply configurable styles with sidebar layout. Box-shadow is\n      // normally on panel, but a detached split cards the chat column here.\n      container.style.cssText = `\n        display: flex !important;\n        flex-direction: column !important;\n        flex: 1 1 0% !important;\n        width: 100% !important;\n        height: 100% !important;\n        min-height: 0 !important;\n        max-height: 100% !important;\n        overflow: hidden !important;\n        border-radius: ${chatContainerRadius} !important;\n        border: ${chatContainerBorder} !important;\n        ${detachedSplitActive && !chatFlush ? `box-shadow: ${chatCardShadow} !important;` : ''}\n        ${chatFlush ? 'background: transparent !important;' : ''}\n      `;\n      \n      // Remove footer border in sidebar mode\n      footer.style.cssText = `\n        flex-shrink: 0 !important;\n        border-top: none !important;\n        padding: 8px 16px 12px 16px !important;\n        ${chatFlush ? 'background: transparent !important;' : ''}\n      `;\n    }\n    \n    // Apply max-height constraints to wrapper to prevent expanding past viewport top\n    // Use both -moz-available (Firefox) and stretch (standard) for cross-browser support\n    // Append to cssText to allow multiple fallback values for the same property\n    // Only apply to launcher mode (not sidebar or inline embed)\n    if (!isInlineEmbed && !dockedMode) {\n      const maxHeightStyles = 'max-height: -moz-available !important; max-height: stretch !important;';\n      const paddingStyles = sidebarMode ? '' : 'padding-top: 1.25em !important;';\n      const zIndexStyles = !sidebarMode\n        ? `z-index: ${config.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX} !important;`\n        : '';\n      wrapper.style.cssText += maxHeightStyles + paddingStyles + zIndexStyles;\n    }\n\n    restoreBodyScrollTop();\n  };\n  applyFullHeightStyles();\n  // Apply theme variables after applyFullHeightStyles since it resets mount.style.cssText\n  applyThemeVariables(mount, config);\n  applyArtifactLayoutCssVars(mount, config);\n  applyArtifactPaneAppearance(mount, config);\n\n  // applyFullHeightStyles wipes mount.style.cssText, so re-apply the theme +\n  // artifact layout vars after it, mirroring the init sequence above.\n  syncPanelChrome = () => {\n    applyFullHeightStyles();\n    applyThemeVariables(mount, config);\n    applyArtifactLayoutCssVars(mount, config);\n    applyArtifactPaneAppearance(mount, config);\n    // Owned here so it lands after applyArtifactPaneAppearance and derives from\n    // the same resolved panel radius the chat card uses (see applyFullHeightStyles).\n    if (weldedOuterRadius) {\n      mount.style.setProperty(\"--persona-artifact-welded-outer-radius\", weldedOuterRadius);\n    } else {\n      mount.style.removeProperty(\"--persona-artifact-welded-outer-radius\");\n    }\n  };\n\n  const destroyCallbacks: Array<() => void> = [];\n  // Clean up the document-level digit-key shortcut listener registered earlier.\n  destroyCallbacks.push(() => {\n    document.removeEventListener(\"keydown\", handleAskUserDigitKey);\n  });\n  // Clear any pending live-region announcement timer on teardown.\n  destroyCallbacks.push(() => {\n    if (announceTimer !== null) clearTimeout(announceTimer);\n  });\n\n  let teardownHostStacking: (() => void) | null = null;\n  let releaseScrollLock: (() => void) | null = null;\n\n  destroyCallbacks.push(() => {\n    teardownHostStacking?.();\n    teardownHostStacking = null;\n    releaseScrollLock?.();\n    releaseScrollLock = null;\n  });\n\n  if (artifactPanelResizeObs) {\n    destroyCallbacks.push(() => {\n      artifactPanelResizeObs?.disconnect();\n      artifactPanelResizeObs = null;\n    });\n  }\n\n  destroyCallbacks.push(() => {\n    artifactResizeUnbind?.();\n    artifactResizeUnbind = null;\n    stopArtifactResizePointer();\n    if (artifactResizeHandle) {\n      artifactResizeHandle.remove();\n      artifactResizeHandle = null;\n    }\n    artifactPaneApi?.element.style.removeProperty(\"width\");\n    artifactPaneApi?.element.style.removeProperty(\"maxWidth\");\n  });\n\n  // Event stream cleanup\n  if (showEventStreamToggle) {\n    destroyCallbacks.push(() => {\n      if (eventStreamRAF !== null) {\n        cancelAnimationFrame(eventStreamRAF);\n        eventStreamRAF = null;\n      }\n      eventStreamView?.destroy();\n      eventStreamView = null;\n      eventStreamBuffer?.destroy();\n      eventStreamBuffer = null;\n      eventStreamStore = null;\n    });\n  }\n\n  // Set up theme observer for auto color scheme detection\n  let cleanupThemeObserver: (() => void) | null = null;\n  const setupThemeObserver = () => {\n    // Clean up existing observer if any\n    if (cleanupThemeObserver) {\n      cleanupThemeObserver();\n      cleanupThemeObserver = null;\n    }\n    // Set up new observer if colorScheme is 'auto'\n    if (config.colorScheme === 'auto') {\n      cleanupThemeObserver = createThemeObserver(() => {\n        // Re-apply theme when color scheme changes\n        applyThemeVariables(mount, config);\n      });\n    }\n  };\n  setupThemeObserver();\n  destroyCallbacks.push(() => {\n    if (cleanupThemeObserver) {\n      cleanupThemeObserver();\n      cleanupThemeObserver = null;\n    }\n  });\n\n  // Release this widget's pending built-in approval listeners + \"Allow once\"\n  // popovers if it's destroyed while an approval is still open. Scoped to this\n  // instance's state, so other widgets on the page are unaffected.\n  destroyCallbacks.push(teardownBuiltInApprovals);\n\n  // Activate the stream-animation plugin for this widget instance. Plugins\n  // with `styles` inject their CSS into the widget root once; plugins with\n  // `onAttach` (e.g., glyph-cycle's MutationObserver for real glyph tick\n  // loops) can register long-lived DOM listeners here. Detach callbacks are\n  // deferred to widget destroy.\n  const streamAnimationConfig = config.features?.streamAnimation;\n  if (streamAnimationConfig?.type && streamAnimationConfig.type !== \"none\") {\n    const plugin = resolveStreamAnimationPlugin(\n      streamAnimationConfig.type,\n      streamAnimationConfig.plugins\n    );\n    if (plugin) {\n      ensurePluginActive(plugin, mount);\n      destroyCallbacks.push(() => detachAllPlugins(mount));\n    }\n  }\n\n  const suggestionsManager = createSuggestions(suggestions);\n  let closeHandler: (() => void) | null = null;\n  let session: AgentWidgetSession;\n\n  // Single render rule for the suggestions row, shared by the message-change,\n  // initial-paint, and config-update paths: agent-pushed `suggest_replies`\n  // chips win when the latest-turn rule yields any (last suggest_replies tool\n  // message with no user message after it); otherwise static config chips\n  // keep their before-first-user-message behavior. Config updates MUST route\n  // through here too: re-rendering with only `config.suggestionChips` would\n  // drop a live agent chip row until the next message change.\n  const renderSuggestions = (messages?: AgentWidgetMessage[]) => {\n    if (!session) return;\n    const current = messages ?? session.getMessages();\n    const agentChips =\n      config.features?.suggestReplies?.enabled !== false\n        ? latestAgentSuggestions(current)\n        : null;\n    if (agentChips) {\n      suggestionsManager.render(\n        agentChips,\n        session,\n        textarea,\n        current,\n        config.suggestionChipsConfig,\n        { agentPushed: true }\n      );\n    } else if (current.some((msg) => msg.role === \"user\")) {\n      // Hide suggestions once a user message exists.\n      suggestionsManager.render([], session, textarea, current);\n    } else {\n      suggestionsManager.render(\n        config.suggestionChips,\n        session,\n        textarea,\n        current,\n        config.suggestionChipsConfig\n      );\n    }\n  };\n  let isStreaming = false;\n  const messageCache = createMessageCache();\n  // Tracks the last fingerprint we rendered a plugin-rendered ask_user_question\n  // bubble for, per message id. Lets us skip unnecessary rebuilds across\n  // re-renders so user state inside the plugin (typed text, focus) survives.\n  const lastAskBubbleFingerprint = new Map<string, string>();\n  // Same idea for component-directive bubbles (registered custom components\n  // rendered from JSON directives). The renderer's element is injected into the\n  // live DOM post-morph so its event listeners survive; this map gates the\n  // expensive rebuild on fingerprint change so user state inside the rendered\n  // component (e.g. partially-filled form inputs) is not wiped on every pass.\n  const lastComponentDirectiveFingerprint = new Map<string, string>();\n  // Same idea for plugin-rendered approval bubbles (`renderApproval`). The\n  // custom element is injected into the live DOM post-morph so its event\n  // listeners (Approve/Deny, an expandable parameters accordion, etc.) survive;\n  // this map gates the rebuild on fingerprint change so interactive state (e.g.\n  // a collapsed accordion) is not reset on every pass while the approval is\n  // pending.\n  const lastApprovalBubbleFingerprint = new Map<string, string>();\n  let configVersion = 0;\n  // Whether the markdown parsers (marked + dompurify) were already loaded when\n  // this widget mounted. False only on the IIFE/CDN lazy path before the\n  // `markdown-parsers.js` chunk resolves; in that window messages render as\n  // escaped plain text and are re-rendered once the chunk lands (see below).\n  const markdownReadyAtInit = getMarkdownParsersSync() !== null;\n  const autoFollow = createFollowStateController();\n  let lastScrollTop = 0;\n  let scrollRAF: number | null = null;\n  let isAutoScrolling = false;\n  let hasPendingAutoScroll = false;\n  // Messages that arrived while the user was away from the latest content;\n  // shown as a badge on the scroll-to-bottom affordance.\n  let newMessagesSincePause = 0;\n  // Live anchor-top state for the current turn (null when not anchored).\n  let anchorState: {\n    initialSpacerHeight: number;\n    contentHeightAtAnchor: number;\n    spacerHeight: number;\n  } | null = null;\n  let anchorRAF: number | null = null;\n  // Seeded send-detection so restored history doesn't read as a fresh send.\n  let scrollSendSeeded = false;\n  let suppressScrollSend = false;\n  let lastSentUserMessageId: string | null = null;\n  // anchor-top no-anchor fallback: anchor-top pins on a USER send. An assistant\n  // message that streams when NO user send has anchored the conversation yet\n  // (first-load / proactive-first streaming) has nothing to anchor to, so it\n  // falls back to follow-to-bottom — otherwise its content streams in\n  // off-screen. `true` by default (nothing anchored yet); a user send clears it\n  // and the anchor takes over. Inert in follow/none mode (see\n  // `isFollowEffective`).\n  let followFallbackActive = true;\n  // True once a user send has anchored the current conversation (until the chat\n  // is cleared). While anchored, follow-on assistant content — the response, a\n  // multi-part reply, an injected embed (tweet/image), a tool result — stays\n  // pinned and never re-arms the fallback, so a late-loading embed can't yank\n  // the viewport down to the bottom.\n  let currentTurnAnchored = false;\n  // Dedupes assistant-turn detection across token-by-token re-renders.\n  let lastHandledAssistantId: string | null = null;\n\n  // Scroll events caused by layout, scroll anchoring, and smooth-scroll\n  // easing can easily move by a couple pixels. Keep manual wheel intent\n  // responsive, but require a slightly larger raw scroll delta before we\n  // treat a plain scroll event as the user breaking away.\n  const USER_SCROLL_THRESHOLD = 4;\n  const BOTTOM_THRESHOLD = 24;\n  const AUTO_SCROLL_SNAP_THRESHOLD = 80;\n  const messageState = new Map<\n    string,\n    { streaming?: boolean; role: AgentWidgetMessage[\"role\"] }\n  >();\n  const voiceState = {\n    active: false,\n    manuallyDeactivated: false,\n    lastUserMessageWasVoice: false,\n    lastUserMessageId: null as string | null\n  };\n  const voiceAutoResumeMode = config.voiceRecognition?.autoResume ?? false;\n  const emitVoiceState = (source: AgentWidgetVoiceStateEvent[\"source\"]) => {\n    eventBus.emit(\"voice:state\", {\n      active: voiceState.active,\n      source,\n      timestamp: Date.now()\n    });\n  };\n  const persistVoiceMetadata = () => {\n    updateSessionMetadata((prev) => ({\n      ...prev,\n      voiceState: {\n        active: voiceState.active,\n        timestamp: Date.now(),\n        manuallyDeactivated: voiceState.manuallyDeactivated\n      }\n    }));\n  };\n  const maybeRestoreVoiceFromMetadata = () => {\n    if (config.voiceRecognition?.enabled === false) return;\n    const rawVoiceState = ensureRecord((persistentMetadata as any).voiceState);\n    const wasActive = Boolean(rawVoiceState.active);\n    const timestamp = Number(rawVoiceState.timestamp ?? 0);\n    voiceState.manuallyDeactivated = Boolean(rawVoiceState.manuallyDeactivated);\n    if (wasActive && Date.now() - timestamp < VOICE_STATE_RESTORE_WINDOW) {\n      setTimeout(() => {\n        if (!voiceState.active) {\n          voiceState.manuallyDeactivated = false;\n          if (config.voiceRecognition?.provider?.type === 'runtype') {\n            session.toggleVoice().then(() => {\n              voiceState.active = session.isVoiceActive();\n              emitVoiceState(\"restore\");\n              if (session.isVoiceActive()) applyRuntypeMicRecordingStyles();\n            });\n          } else {\n            startVoiceRecognition(\"restore\");\n          }\n        }\n      }, 1000);\n    }\n  };\n\n  const getMessagesForPersistence = () =>\n    session \n      ? stripStreamingFromMessages(session.getMessages()).filter(msg => !(msg as any).__skipPersist)\n      : [];\n\n  let storageMutationTail: Promise<void> | null = null;\n\n  const reportStorageMutationError = (label: string, error: unknown) => {\n    if (typeof console !== \"undefined\") {\n      // eslint-disable-next-line no-console\n      console.error(label, error);\n    }\n  };\n\n  const runStorageMutation = (\n    mutation: () => void | Promise<void>,\n    errorLabel: string\n  ) => {\n    const run = (): Promise<void> | null => {\n      try {\n        const result = mutation();\n        if (!result || typeof (result as PromiseLike<void>).then !== \"function\") {\n          return null;\n        }\n        return Promise.resolve(result).catch((error) => {\n          reportStorageMutationError(errorLabel, error);\n        });\n      } catch (error) {\n        reportStorageMutationError(errorLabel, error);\n        return null;\n      }\n    };\n\n    const prior = storageMutationTail;\n    const next = prior\n      ? prior.then(() => run() ?? undefined)\n      : run();\n    if (!next) return;\n\n    const tail = next.finally(() => {\n      if (storageMutationTail === tail) {\n        storageMutationTail = null;\n      }\n    });\n    storageMutationTail = tail;\n  };\n\n  function persistState(messagesOverride?: AgentWidgetMessage[]) {\n    if (!storageAdapter?.save) return;\n\n    // Allow saving even if session doesn't exist yet (for metadata during init)\n    const messages = messagesOverride\n      ? stripStreamingFromMessages(messagesOverride)\n      : session\n        ? getMessagesForPersistence()\n        : [];\n\n    const payload = {\n      messages,\n      metadata: persistentMetadata,\n      artifacts: lastArtifactsState.artifacts,\n      selectedArtifactId: lastArtifactsState.selectedId\n    };\n    runStorageMutation(\n      () => storageAdapter.save!(payload),\n      \"[AgentWidget] Failed to persist state:\"\n    );\n  }\n\n  // Track ongoing smooth scroll animation\n  let smoothScrollRAF: number | null = null;\n\n  // Get the scrollable container using its unique ID\n  const getScrollableContainer = (): HTMLElement => {\n    // Use the unique ID for reliable selection\n    const scrollable = wrapper.querySelector('#persona-scroll-container') as HTMLElement;\n    // Fallback to body if ID not found (shouldn't happen, but safe fallback)\n    return scrollable || body;\n  };\n\n  const cancelSmoothScroll = () => {\n    if (smoothScrollRAF !== null) {\n      cancelAnimationFrame(smoothScrollRAF);\n      smoothScrollRAF = null;\n    }\n    isAutoScrolling = false;\n  };\n\n  const cancelAutoScroll = () => {\n    if (scrollRAF !== null) {\n      cancelAnimationFrame(scrollRAF);\n      scrollRAF = null;\n    }\n    hasPendingAutoScroll = false;\n    cancelSmoothScroll();\n  };\n\n  // True when a response is streaming in below the reader's current position,\n  // i.e. content is arriving out of view. Drives the \"still streaming\" hint on\n  // the scroll-to-bottom affordance (Principle 8: show what's happening out of\n  // view). In anchor-top mode this is gated behind `showActivityWhilePinned`\n  // so the historical \"silent while pinned\" behavior is preserved by default.\n  const isStreamingOutOfView = () =>\n    isStreaming &&\n    isAwayFromLatest() &&\n    (getScrollMode() !== \"anchor-top\" || isActivityWhilePinnedEnabled());\n\n  const updateScrollToBottomCountBadge = () => {\n    const base = getScrollToBottomLabel() || \"Jump to latest\";\n    const streamingBelow = isStreamingOutOfView();\n    scrollToBottomButton.toggleAttribute(\n      \"data-persona-scroll-to-bottom-streaming\",\n      streamingBelow\n    );\n    if (newMessagesSincePause > 0) {\n      scrollToBottomCount.textContent = String(newMessagesSincePause);\n      scrollToBottomCount.style.display = \"\";\n      scrollToBottomButton.setAttribute(\n        \"aria-label\",\n        `${base} (${newMessagesSincePause} new)`\n      );\n    } else {\n      scrollToBottomCount.textContent = \"\";\n      scrollToBottomCount.style.display = \"none\";\n      scrollToBottomButton.setAttribute(\n        \"aria-label\",\n        streamingBelow ? `${base} (response streaming below)` : base\n      );\n    }\n  };\n\n  const resetNewMessagesCount = () => {\n    if (newMessagesSincePause === 0) return;\n    newMessagesSincePause = 0;\n    updateScrollToBottomCountBadge();\n  };\n\n  // Whether the user is currently away from the latest content: drives both\n  // the scroll-to-bottom affordance and the new-messages badge. When following\n  // the bottom (follow mode, or a no-anchor anchor-top fallback turn) that's\n  // \"auto-follow paused\"; otherwise it's simply \"not near the bottom\".\n  const isAwayFromLatest = () =>\n    isFollowEffective()\n      ? !autoFollow.isFollowing()\n      : !isElementNearBottom(body, BOTTOM_THRESHOLD);\n\n  const syncScrollToBottomButton = () => {\n    if (!isScrollToBottomEnabled() || eventStreamVisible) {\n      if (scrollToBottomButton.parentNode) {\n        scrollToBottomButton.remove();\n      }\n      scrollToBottomButton.style.display = \"none\";\n      return;\n    }\n    if (scrollToBottomButton.parentNode !== container) {\n      container.appendChild(scrollToBottomButton);\n    }\n    updateScrollToBottomButtonOffset();\n    const hasOverflow = getScrollBottomOffset(body) > 0;\n    const show = hasOverflow && isAwayFromLatest();\n    if (!show) {\n      resetNewMessagesCount();\n    } else {\n      // Refresh the streaming-below hint while the affordance is visible.\n      updateScrollToBottomCountBadge();\n    }\n    scrollToBottomButton.style.display = show ? \"\" : \"none\";\n  };\n\n  const pauseAutoScroll = () => {\n    if (!autoFollow.pause()) return;\n    cancelAutoScroll();\n    syncScrollToBottomButton();\n  };\n\n  const resumeAutoScroll = () => {\n    autoFollow.resume();\n    resetNewMessagesCount();\n    syncScrollToBottomButton();\n  };\n\n  const scheduleAutoScroll = (force = false) => {\n    // Auto-follow applies in \"follow\" mode, and in anchor-top only for a\n    // no-anchor fallback turn (see `isFollowEffective`). Anchored anchor-top\n    // turns and \"none\" never chase the bottom during streaming.\n    if (!isFollowEffective()) return;\n\n    if (!autoFollow.isFollowing()) return;\n\n    if (!force && !isStreaming) return;\n\n    // Only cancel the pending schedule rAF: keep the ongoing smooth scroll\n    // animation alive so isAutoScrolling stays true.  This prevents scroll\n    // events fired by DOM morphing (between cancel and the next rAF) from\n    // being misinterpreted as user-initiated upward scrolls that would\n    // permanently pause auto-follow during streaming.\n    // smoothScrollToBottom() already calls cancelSmoothScroll() internally\n    // before starting its new animation.\n    if (scrollRAF !== null) {\n      cancelAnimationFrame(scrollRAF);\n      scrollRAF = null;\n    }\n\n    // Treat the render -> next-rAF window as programmatic scrolling too.\n    // This prevents layout/scroll-anchoring scroll events fired before the\n    // actual smooth scroll starts from being misread as user intent.\n    hasPendingAutoScroll = true;\n    scrollRAF = requestAnimationFrame(() => {\n      scrollRAF = null;\n      hasPendingAutoScroll = false;\n      if (!autoFollow.isFollowing()) return;\n      smoothScrollToBottom(getScrollableContainer(), force ? 220 : 140);\n    });\n  };\n\n  // Generic eased scroll animation. `resolveTarget` is re-read every frame so\n  // a moving target (the bottom of a streaming transcript) stays accurate;\n  // `shouldContinue` lets the caller cancel mid-flight (e.g. when auto-follow\n  // pauses). Scroll events emitted by the animation are masked from the\n  // user-intent detector via `isAutoScrolling`.\n  const animateScrollTo = (\n    element: HTMLElement,\n    resolveTarget: () => number,\n    duration: number,\n    shouldContinue: () => boolean = () => true\n  ) => {\n    const start = element.scrollTop;\n    let target = resolveTarget();\n    let distance = target - start;\n\n    // Cancel any ongoing smooth scroll animation\n    cancelSmoothScroll();\n\n    // Nothing to scroll: land exactly on target and skip the rAF loop. Avoids a\n    // no-op animation when already in place (e.g. anchoring with zero overflow),\n    // which also keeps environments with a synchronous rAF from spinning.\n    if (Math.abs(distance) < 1) {\n      isAutoScrolling = true;\n      element.scrollTop = target;\n      lastScrollTop = element.scrollTop;\n      isAutoScrolling = false;\n      return;\n    }\n\n    const startTime = performance.now();\n    isAutoScrolling = true;\n\n    // Easing function: ease-out cubic for smooth deceleration\n    const easeOutCubic = (t: number): number => {\n      return 1 - Math.pow(1 - t, 3);\n    };\n\n    const animate = (currentTime: number) => {\n      if (!shouldContinue()) {\n        cancelSmoothScroll();\n        return;\n      }\n\n      // Recalculate target each frame in case scrollHeight changed\n      const currentTarget = resolveTarget();\n      if (currentTarget !== target) {\n        target = currentTarget;\n        distance = target - start;\n      }\n\n      const elapsed = currentTime - startTime;\n      const progress = Math.min(elapsed / duration, 1);\n      const eased = easeOutCubic(progress);\n\n      const currentScroll = start + distance * eased;\n      element.scrollTop = currentScroll;\n      lastScrollTop = element.scrollTop;\n\n      if (progress < 1) {\n        smoothScrollRAF = requestAnimationFrame(animate);\n      } else {\n        // Ensure we end exactly at the target\n        element.scrollTop = target;\n        lastScrollTop = element.scrollTop;\n        smoothScrollRAF = null;\n        isAutoScrolling = false;\n      }\n    };\n\n    smoothScrollRAF = requestAnimationFrame(animate);\n  };\n\n  // Custom smooth scroll animation with easing\n  const smoothScrollToBottom = (element: HTMLElement, duration = 500) => {\n    const distance = getScrollBottomOffset(element) - element.scrollTop;\n\n    // If already at bottom or very close, skip animation to prevent glitch\n    if (Math.abs(distance) < 1) {\n      lastScrollTop = element.scrollTop;\n      return;\n    }\n\n    // If the transcript has fallen noticeably behind, catch up immediately\n    // instead of easing over multiple frames. This keeps fast streaming /\n    // bursty tool and reasoning updates pinned to the bottom.\n    if (Math.abs(distance) >= AUTO_SCROLL_SNAP_THRESHOLD) {\n      cancelSmoothScroll();\n      isAutoScrolling = true;\n      element.scrollTop = getScrollBottomOffset(element);\n      lastScrollTop = element.scrollTop;\n      isAutoScrolling = false;\n      return;\n    }\n\n    animateScrollTo(\n      element,\n      () => getScrollBottomOffset(element),\n      duration,\n      () => autoFollow.isFollowing()\n    );\n  };\n\n  // Instant jump used for initial mount / panel open in non-follow scroll\n  // modes (where scheduleAutoScroll is inert).\n  const jumpToBottomInstant = () => {\n    const element = getScrollableContainer();\n    isAutoScrolling = true;\n    element.scrollTop = getScrollBottomOffset(element);\n    lastScrollTop = element.scrollTop;\n    isAutoScrolling = false;\n    syncScrollToBottomButton();\n  };\n\n  // Walk offsetParents up to `body` (the positioned scroll ancestor) to get a\n  // node's top relative to the scroll content. offsetTop avoids skew from any\n  // in-flight entrance transforms. Mirrors the anchor-top geometry.\n  const offsetTopWithinBody = (el: HTMLElement): number => {\n    let top = 0;\n    let node: HTMLElement | null = el;\n    while (node && node !== body) {\n      top += node.offsetTop;\n      node = node.offsetParent as HTMLElement | null;\n    }\n    return top;\n  };\n\n  // Principle 11: reopen where the reader left off. When `restorePosition` is\n  // \"last-user-turn\" and there is pre-existing history, land with the last user\n  // message pinned near the top of the viewport instead of jumping to the\n  // absolute bottom. Returns true when it handled positioning. Opt-in; the\n  // default (\"bottom\") returns false so callers fall back to jump-to-bottom.\n  const restoreScrollPosition = (): boolean => {\n    if (getScrollRestorePosition() !== \"last-user-turn\") return false;\n    const messages = session?.getMessages() ?? [];\n    // A *restore* only makes sense when reopening existing history; a fresh\n    // (empty or single-turn) conversation should still start at the latest.\n    if (messages.length < 2) return false;\n    const lastUser = [...messages].reverse().find((m) => m.role === \"user\");\n    if (!lastUser) return false;\n    const escapedId =\n      typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\"\n        ? CSS.escape(lastUser.id)\n        : lastUser.id.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n    const bubble = body.querySelector<HTMLElement>(\n      `[data-message-id=\"${escapedId}\"]`\n    );\n    if (!bubble) return false;\n    const target = Math.min(\n      Math.max(0, offsetTopWithinBody(bubble) - getAnchorTopOffset()),\n      getScrollBottomOffset(body)\n    );\n    isAutoScrolling = true;\n    body.scrollTop = target;\n    lastScrollTop = body.scrollTop;\n    isAutoScrolling = false;\n    // In follow mode, deliberately landing above the bottom means we are not\n    // following; pause so the first streamed token doesn't yank the reader\n    // down. (In anchor-top/none there is no follow state to manage.)\n    if (\n      getScrollMode() === \"follow\" &&\n      !isElementNearBottom(body, BOTTOM_THRESHOLD)\n    ) {\n      autoFollow.pause();\n    }\n    syncScrollToBottomButton();\n    return true;\n  };\n\n  const setAnchorSpacerHeight = (height: number) => {\n    anchorSpacer.style.height = `${Math.max(0, Math.round(height))}px`;\n    if (anchorState) {\n      anchorState.spacerHeight = Math.max(0, height);\n    }\n  };\n\n  const resetAnchorState = () => {\n    if (anchorRAF !== null) {\n      cancelAnimationFrame(anchorRAF);\n      anchorRAF = null;\n    }\n    // Also stop an in-flight anchor scroll animation: otherwise its\n    // remaining frames keep easing scrollTop toward the stale anchor target\n    // after a jump-to-latest, chat clear, or scroll-mode change.\n    cancelSmoothScroll();\n    anchorState = null;\n    anchorSpacer.style.height = \"0px\";\n  };\n\n  // Anchor-top mode: scroll the just-sent user message to rest\n  // `anchorTopOffset` px below the viewport top and hold it there while the\n  // response streams in beneath it. Deferred one frame so the message bubble\n  // has been rendered and laid out.\n  const scheduleAnchorToUserMessage = (messageId: string) => {\n    if (anchorRAF !== null) {\n      cancelAnimationFrame(anchorRAF);\n    }\n    anchorRAF = requestAnimationFrame(() => {\n      anchorRAF = null;\n      const escapedId =\n        typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\"\n          ? CSS.escape(messageId)\n          : messageId.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n      const bubble = body.querySelector<HTMLElement>(\n        `[data-message-id=\"${escapedId}\"]`\n      );\n      if (!bubble) return;\n\n      // Bubble top relative to the scroll content. offsetTop is used instead\n      // of getBoundingClientRect so in-flight entrance animations (transforms)\n      // can't skew the target.\n      const anchorOffsetTop = offsetTopWithinBody(bubble);\n\n      const previousSpacerHeight = anchorState?.spacerHeight ?? 0;\n      const contentHeight = body.scrollHeight - previousSpacerHeight;\n      const { targetScrollTop, spacerHeight } = computeAnchorScrollState({\n        anchorOffsetTop,\n        topOffset: getAnchorTopOffset(),\n        viewportHeight: body.clientHeight,\n        contentHeight\n      });\n\n      anchorState = {\n        initialSpacerHeight: spacerHeight,\n        contentHeightAtAnchor: contentHeight,\n        spacerHeight\n      };\n      setAnchorSpacerHeight(spacerHeight);\n      animateScrollTo(body, () => targetScrollTop, 220);\n    });\n  };\n\n  // Content growth handler (ResizeObserver-driven). In follow mode this is\n  // what keeps the transcript pinned when content grows *without* a render\n  // event: images/embeds finishing loading mid-stream, fonts swapping,\n  // the panel or composer resizing. In anchor-top mode it gives spacer room\n  // back as the streamed response grows (shrink-only, so total scroll height\n  // stays constant and nothing jumps).\n  const handleContentResize = () => {\n    if (isFollowEffective()) {\n      if (!autoFollow.isFollowing()) return;\n      if (isElementNearBottom(body, 1)) return;\n      scheduleAutoScroll(!isStreaming);\n      return;\n    }\n    if (anchorState && anchorState.initialSpacerHeight > 0) {\n      const currentContentHeight = body.scrollHeight - anchorState.spacerHeight;\n      const next = computeShrunkSpacerHeight({\n        initialSpacerHeight: anchorState.initialSpacerHeight,\n        contentHeightAtAnchor: anchorState.contentHeightAtAnchor,\n        currentContentHeight\n      });\n      if (next !== anchorState.spacerHeight) {\n        setAnchorSpacerHeight(next);\n      }\n    }\n    syncScrollToBottomButton();\n  };\n\n  // Reacts to a user message the user just sent (seeded so restored history\n  // never triggers it). Follow mode re-sticks to the bottom even if the user\n  // had scrolled up: sending is an unambiguous \"take me to the latest\"\n  // signal. Anchor-top mode pins the sent message near the viewport top.\n  const handleUserMessageSent = (messageId: string) => {\n    const mode = getScrollMode();\n    if (mode === \"follow\") {\n      resumeAutoScroll();\n      scheduleAutoScroll(true);\n    } else if (mode === \"anchor-top\") {\n      // A real anchor now drives the conversation: disarm the no-anchor\n      // fallback. Every follow-on assistant message stays anchored until the\n      // next user send.\n      followFallbackActive = false;\n      currentTurnAnchored = true;\n      scheduleAnchorToUserMessage(messageId);\n    }\n  };\n\n  // Reacts to a new assistant message that arrived without a fresh user send.\n  // Only meaningful in anchor-top. While the conversation is anchored (a user\n  // has sent at least once), follow-on assistant content — the response, a\n  // multi-part reply, an injected embed, a tool result — keeps the anchor so a\n  // late-loading embed never yanks the viewport. Only when nothing has anchored\n  // yet (first-load / proactive-first streaming) does it fall back to\n  // follow-to-bottom so the content isn't stranded off-screen.\n  const handleAssistantTurnStarted = () => {\n    if (getScrollMode() !== \"anchor-top\") return;\n    if (currentTurnAnchored) {\n      followFallbackActive = false;\n      return;\n    }\n    followFallbackActive = true;\n    resetAnchorState();\n    resumeAutoScroll();\n    scheduleAutoScroll(true);\n  };\n\n  const trackMessages = (messages: AgentWidgetMessage[]) => {\n    const nextState = new Map<\n      string,\n      { streaming?: boolean; role: AgentWidgetMessage[\"role\"] }\n    >();\n\n    messages.forEach((message) => {\n      const previous = messageState.get(message.id);\n      nextState.set(message.id, {\n        streaming: message.streaming,\n        role: message.role\n      });\n\n      if (!previous && message.role === \"assistant\") {\n        eventBus.emit(\"assistant:message\", message);\n        // Count messages the user hasn't seen for the scroll-to-bottom badge.\n        // Skipped in anchor-top (the user is already reading the latest turn\n        // from its top, so a \"new\" count would normally mislead) and during\n        // history hydration (restored messages aren't \"missed\"). When\n        // `showActivityWhilePinned` is opted in, anchor-top *does* count so the\n        // reader is told content is arriving offscreen below (Principle 8).\n        if (\n          !suppressScrollSend &&\n          (getScrollMode() !== \"anchor-top\" || isActivityWhilePinnedEnabled()) &&\n          isAwayFromLatest()\n        ) {\n          newMessagesSincePause += 1;\n          updateScrollToBottomCountBadge();\n          syncScrollToBottomButton();\n          announce(\n            newMessagesSincePause === 1\n              ? \"1 new message below.\"\n              : `${newMessagesSincePause} new messages below.`\n          );\n        }\n      }\n\n      if (\n        message.role === \"assistant\" &&\n        previous?.streaming &&\n        message.streaming === false\n      ) {\n        eventBus.emit(\"assistant:complete\", message);\n      }\n\n      // Emit approval events\n      if (message.variant === \"approval\" && message.approval) {\n        if (!previous) {\n          eventBus.emit(\"approval:requested\", { approval: message.approval, message });\n        } else if (message.approval.status !== \"pending\") {\n          eventBus.emit(\"approval:resolved\", { approval: message.approval, decision: message.approval.status });\n        }\n      }\n    });\n\n    messageState.clear();\n    nextState.forEach((value, key) => {\n      messageState.set(key, value);\n    });\n  };\n\n\n  // Message rendering with plugin support (implementation)\n  const renderMessagesWithPluginsImpl = (\n    container: HTMLElement,\n    messages: AgentWidgetMessage[],\n    transform: MessageTransform\n  ) => {\n    // Build new content in a temporary container for morphing\n    const tempContainer = document.createElement(\"div\");\n\n    // Create inline loading indicator renderer using priority chain: plugin -> config -> default\n    const getInlineLoadingIndicatorRenderer = (): LoadingIndicatorRenderer | undefined => {\n      // Check if any plugin has renderLoadingIndicator\n      const loadingPlugin = plugins.find(p => p.renderLoadingIndicator);\n      if (loadingPlugin?.renderLoadingIndicator) {\n        return loadingPlugin.renderLoadingIndicator;\n      }\n\n      // Check if config has loadingIndicator.render\n      if (config.loadingIndicator?.render) {\n        return config.loadingIndicator.render;\n      }\n\n      // Return undefined to use default in createStandardBubble\n      return undefined;\n    };\n\n    const inlineLoadingRenderer = getInlineLoadingIndicatorRenderer();\n    const appendRenderedValue = (\n      containerEl: HTMLElement,\n      value: HTMLElement | string | null | undefined\n    ): boolean => {\n      if (value == null) return false;\n      if (typeof value === \"string\") {\n        containerEl.textContent = value;\n        return true;\n      }\n      containerEl.appendChild(value);\n      return true;\n    };\n\n    // Track active message IDs for cache pruning\n    const activeMessageIds = new Set<string>();\n    // Track ask_user_question tool-call ids whose bubbles were rendered this\n    // pass: used to prune stale sheets from the composer overlay afterward.\n    const liveAskToolIds = new Set<string>();\n\n    // Plugins that render `ask_user_question` typically attach DOM listeners\n    // directly to their buttons. The wrapper cache uses `cloneNode(true)` and\n    // idiomorph inserts new nodes via `document.importNode`: both strip\n    // listeners. For plugin-handled ask messages we therefore append an empty\n    // stub during the morph pass and hydrate the live plugin bubble into the\n    // morphed wrapper afterward (see post-morph loop below). The stub carries\n    // `data-preserve-runtime` so subsequent passes leave the live wrapper\n    // (with its listener-bearing bubble) untouched.\n    const hasAskPlugin = plugins.some((p) => p.renderAskUserQuestion);\n    type AskPluginHydrate = {\n      messageId: string;\n      fingerprint: string;\n      bubble: HTMLElement | null;\n    };\n    const askPluginHydrate: AskPluginHydrate[] = [];\n\n    // Component-directive bubbles use the same stub-and-hydrate pattern as\n    // ask_user_question plugins: the renderer's HTMLElement is built live and\n    // injected into the morphed wrapper afterward, so listeners attached via\n    // `addEventListener` (e.g. form `submit` handlers) survive transcript\n    // morphs. `bubble: null` means the fingerprint matched a previous pass and\n    // the live wrapper is reused as-is.\n    type ComponentDirectiveHydrate = {\n      messageId: string;\n      fingerprint: string;\n      bubble: HTMLElement | null;\n    };\n    const componentDirectiveHydrate: ComponentDirectiveHydrate[] = [];\n    const componentStreamingEnabled = config.enableComponentStreaming !== false;\n\n    // Plugin-rendered approval bubbles use the same stub-and-hydrate pattern:\n    // `renderApproval` may attach listeners (the built-in bubble resolves via\n    // delegation on `messagesWrapper`, but a custom element owns its own\n    // interactivity), and idiomorph imports nodes via `document.importNode`,\n    // which strips them. So we build the live element, append a stub during\n    // morph, and inject the live element afterward.\n    // The built-in approval renderer is always available (as a fallback plugin),\n    // so every approval flows through the stub-and-hydrate path whenever\n    // approvals are enabled — a user `renderApproval` plugin just overrides it.\n    const hasApprovalPlugin = config.approval !== false;\n    type ApprovalPluginHydrate = {\n      messageId: string;\n      fingerprint: string;\n      bubble: HTMLElement | null;\n    };\n    const approvalPluginHydrate: ApprovalPluginHydrate[] = [];\n\n    messages.forEach((message) => {\n      activeMessageIds.add(message.id);\n\n      const askWithPlugin = hasAskPlugin && isAskUserQuestionMessage(message);\n      const approvalWithPlugin =\n        hasApprovalPlugin && message.variant === \"approval\" && !!message.approval;\n      const hasDirectiveBubble =\n        !askWithPlugin &&\n        message.role === \"assistant\" &&\n        !message.variant &&\n        componentStreamingEnabled &&\n        hasComponentDirective(message);\n\n      // If a message stops being an approval-plugin bubble, strip\n      // `data-preserve-runtime` so the next morph can replace the live wrapper.\n      if (!approvalWithPlugin && lastApprovalBubbleFingerprint.has(message.id)) {\n        const existing = container.querySelector<HTMLElement>(`#wrapper-${message.id}`);\n        existing?.removeAttribute(\"data-preserve-runtime\");\n        lastApprovalBubbleFingerprint.delete(message.id);\n      }\n\n      // If a message previously rendered as a directive bubble but no longer\n      // does (e.g. content was rewritten), strip `data-preserve-runtime` from\n      // the live wrapper so the next morph can replace it.\n      if (!hasDirectiveBubble && lastComponentDirectiveFingerprint.has(message.id)) {\n        const existing = container.querySelector<HTMLElement>(`#wrapper-${message.id}`);\n        existing?.removeAttribute(\"data-preserve-runtime\");\n        lastComponentDirectiveFingerprint.delete(message.id);\n      }\n\n      // Fingerprint cache: skip re-rendering unchanged messages. Append the\n      // ask-user-question answered/answers state so flipping `askUserQuestionAnswered`\n      // (or accumulating answers) busts both the wrapper cache and the plugin's\n      // `lastAskBubbleFingerprint` check, forcing a re-render of the review UX.\n      const askMeta = isAskUserQuestionMessage(message)\n        ? `:${message.agentMetadata?.askUserQuestionAnswered ? \"a\" : \"u\"}:${\n            message.agentMetadata?.askUserQuestionAnswers\n              ? Object.keys(message.agentMetadata.askUserQuestionAnswers).length\n              : 0\n          }`\n        : \"\";\n      const fingerprint = computeMessageFingerprint(message, configVersion) + askMeta;\n      const cachedWrapper = (askWithPlugin || approvalWithPlugin || hasDirectiveBubble)\n        ? null\n        : getCachedWrapper(messageCache, message.id, fingerprint);\n      if (cachedWrapper) {\n        tempContainer.appendChild(cachedWrapper.cloneNode(true));\n        // Keep the overlay sheet alive only while the server is actively\n        // waiting on the user (awaitingLocalTool === true). Before step_await\n        // fires, or after the answer resumes the flow, omit from\n        // liveAskToolIds so the prune loop below removes any stale DOM sheet.\n        // Guards against lingering skeleton sheets from tool_start events\n        // that never get a matching step_await (e.g. LLM-hallucinated trailing\n        // ask_user_question calls at end-of-turn).\n        if (\n          isAskUserQuestionMessage(message) &&\n          message.toolCall?.id &&\n          message.agentMetadata?.awaitingLocalTool === true &&\n          !message.agentMetadata?.askUserQuestionAnswered\n        ) {\n          liveAskToolIds.add(message.toolCall.id);\n          ensureAskUserQuestionSheet(message, config, panelElements.composerOverlay);\n        }\n        return;\n      }\n\n      let bubble: HTMLElement | null = null;\n\n      // Try plugins first\n      const matchingPlugin = plugins.find((p) => {\n        if (message.variant === \"reasoning\" && p.renderReasoning) {\n          return true;\n        }\n        if (message.variant === \"tool\" && p.renderToolCall) {\n          return true;\n        }\n        // Approval plugins are handled via the stub-and-hydrate path below\n        // (see `approvalWithPlugin`), not this inline morph path, so their\n        // listeners survive, so they are intentionally excluded here.\n        if (!message.variant && p.renderMessage) {\n          return true;\n        }\n        return false;\n      });\n\n      // Get message layout config\n      const messageLayoutConfig = config.layout?.messages;\n\n      // ask_user_question has two rendering modes while waiting for an answer:\n      //   1. Plugin `renderAskUserQuestion`: returns an inline transcript\n      //      element with its own UI; the composer-overlay sheet is suppressed.\n      //   2. Built-in composer-overlay answer-pill sheet: no transcript stub.\n      // Plugins win when they return a non-null element; otherwise fall\n      // through to the built-in overlay.\n      //\n      // Once answered, the original tool message is suppressed entirely from\n      // the transcript. `session.resolveAskUserQuestion` injects one assistant\n      // bubble per question and one user bubble per answer (skipped questions\n      // become an italic `*Skipped*` user bubble), so the transcript reads\n      // like a normal Q→A conversation. Plugins do not render the answered\n      // state.\n      if (\n        isAskUserQuestionMessage(message) &&\n        message.agentMetadata?.askUserQuestionAnswered === true\n      ) {\n        // Drop any previously-mounted plugin bubble so the morph pass\n        // removes the now-stale interactive sheet.\n        lastAskBubbleFingerprint.delete(message.id);\n        const existing = container.querySelector<HTMLElement>(`#wrapper-${message.id}`);\n        existing?.removeAttribute(\"data-preserve-runtime\");\n        return;\n      }\n\n      // suggest_replies renders no transcript bubble: the chips above the\n      // composer are the only UI, and the session auto-resumes the call.\n      // When the feature is disabled the message falls through to the generic\n      // tool bubble (and is never auto-resumed), keeping the parked execution\n      // visible instead of silently swallowed.\n      if (\n        isSuggestRepliesMessage(message) &&\n        config.features?.suggestReplies?.enabled !== false\n      ) {\n        return;\n      }\n\n      if (\n        isAskUserQuestionMessage(message) &&\n        config.features?.askUserQuestion?.enabled !== false\n      ) {\n        const askPlugin = plugins.find((p) => typeof p.renderAskUserQuestion === \"function\");\n        if (askPlugin && sessionRef.current) {\n          const lastFp = lastAskBubbleFingerprint.get(message.id);\n          // Whether to actually call the plugin renderer this pass. We do it\n          // on first sight of this message, or when its fingerprint changed\n          // (e.g. payload streamed in more options). Otherwise we rely on the\n          // already-mounted bubble in `container`.\n          const needsRebuild = lastFp !== fingerprint;\n\n          let pluginBubble: HTMLElement | null = null;\n          if (needsRebuild) {\n            const { payload, complete } = parseAskUserQuestionPayload(message);\n            const messageId = message.id;\n            const liveMessage = (): AgentWidgetMessage | undefined =>\n              sessionRef.current?.getMessages().find((m) => m.id === messageId);\n            pluginBubble = askPlugin.renderAskUserQuestion!({\n              message,\n              payload,\n              complete,\n              resolve: (answer) => {\n                const live = liveMessage();\n                if (live) sessionRef.current?.resolveAskUserQuestion(live, answer);\n              },\n              dismiss: () => {\n                const live = liveMessage();\n                if (live?.agentMetadata?.awaitingLocalTool) {\n                  sessionRef.current?.markAskUserQuestionResolved(live);\n                  sessionRef.current?.resolveAskUserQuestion(live, \"(dismissed)\");\n                }\n              },\n              config,\n            });\n          }\n\n          // If the plugin opted out (returned null on a fresh build) AND we\n          // have no previously-mounted bubble for this message, fall back to\n          // the built-in overlay sheet. If we already have a mounted bubble\n          // and the plugin didn't run this pass (cached), keep using it.\n          const previouslyMounted = lastFp != null;\n          if (needsRebuild && pluginBubble === null && !previouslyMounted) {\n            if (\n              message.agentMetadata?.awaitingLocalTool === true &&\n              !message.agentMetadata?.askUserQuestionAnswered\n            ) {\n              liveAskToolIds.add(message.toolCall!.id);\n              ensureAskUserQuestionSheet(message, config, panelElements.composerOverlay);\n            }\n            return;\n          }\n\n          // Append a stub wrapper for the morph pass; hydrate the real bubble\n          // into it post-morph so its event listeners survive.\n          const stub = document.createElement(\"div\");\n          stub.className = \"persona-flex\";\n          stub.id = `wrapper-${message.id}`;\n          stub.setAttribute(\"data-wrapper-id\", message.id);\n          stub.setAttribute(\"data-ask-plugin-stub\", \"true\");\n          stub.setAttribute(\"data-preserve-runtime\", \"true\");\n          tempContainer.appendChild(stub);\n          askPluginHydrate.push({\n            messageId: message.id,\n            fingerprint,\n            bubble: pluginBubble,\n          });\n          return;\n        } else {\n          if (\n            message.agentMetadata?.awaitingLocalTool === true &&\n            !message.agentMetadata?.askUserQuestionAnswered\n          ) {\n            liveAskToolIds.add(message.toolCall!.id);\n            ensureAskUserQuestionSheet(message, config, panelElements.composerOverlay);\n          }\n          return;\n        }\n      } else if (approvalWithPlugin) {\n        // Plugin-rendered approval bubble. Build the live element with its\n        // listeners, append a stub for the morph pass, and hydrate the live\n        // element into the morphed wrapper afterward (same trick as\n        // `renderAskUserQuestion` / component directives) so Approve/Deny and\n        // any accordion listeners survive idiomorph's `importNode`. Gate the\n        // rebuild on fingerprint so interactive state (e.g. a collapsed\n        // accordion) is preserved while the approval stays pending.\n        const approvalPlugin =\n          plugins.find((p) => typeof p.renderApproval === \"function\") ?? builtInApprovalPlugin;\n        const lastFp = lastApprovalBubbleFingerprint.get(message.id);\n        const needsRebuild = lastFp !== fingerprint;\n        let liveBubble: HTMLElement | null = null;\n\n        if (needsRebuild && approvalPlugin?.renderApproval) {\n          // Re-find the live message at decision time so we resolve against\n          // current state, and route WebMCP gate approvals to the local\n          // resolver: mirroring the built-in delegated handler.\n          const approvalMessageId = message.id;\n          const resolveDecision = (\n            decision: \"approved\" | \"denied\",\n            options?: AgentWidgetApprovalDecisionOptions\n          ): void => {\n            const live = sessionRef.current\n              ?.getMessages()\n              .find((m) => m.id === approvalMessageId);\n            if (!live?.approval) return;\n            if (live.approval.toolType === \"webmcp\") {\n              sessionRef.current?.resolveWebMcpApproval(live.id, decision);\n            } else {\n              sessionRef.current?.resolveApproval(live.approval, decision, options);\n            }\n          };\n          liveBubble = approvalPlugin.renderApproval({\n            message,\n            defaultRenderer: () => createApprovalBubble(message, config),\n            config,\n            approve: (options) => resolveDecision(\"approved\", options),\n            deny: (options) => resolveDecision(\"denied\", options)\n          });\n        }\n\n        if (needsRebuild && liveBubble === null) {\n          // Plugin opted out for this state (e.g. a resolved approval, where the\n          // demo plugin defers to the built-in approved/denied bubble). Render\n          // the built-in bubble: it resolves via the delegated `messagesWrapper`\n          // handler and morphs normally, and drop any preserved live wrapper so\n          // morph can replace the now-stale pending bubble.\n          const existing = container.querySelector<HTMLElement>(`#wrapper-${message.id}`);\n          existing?.removeAttribute(\"data-preserve-runtime\");\n          lastApprovalBubbleFingerprint.delete(message.id);\n          bubble = createApprovalBubble(message, config);\n        } else {\n          // A fresh live bubble to hydrate (needsRebuild), or fingerprint\n          // unchanged so we reuse the preserved live wrapper (`bubble: null`).\n          const stub = document.createElement(\"div\");\n          stub.className = \"persona-flex\";\n          stub.id = `wrapper-${message.id}`;\n          stub.setAttribute(\"data-wrapper-id\", message.id);\n          stub.setAttribute(\"data-approval-plugin-stub\", \"true\");\n          stub.setAttribute(\"data-preserve-runtime\", \"true\");\n          tempContainer.appendChild(stub);\n          approvalPluginHydrate.push({\n            messageId: message.id,\n            fingerprint,\n            bubble: liveBubble\n          });\n          return;\n        }\n      } else if (matchingPlugin) {\n        if (message.variant === \"reasoning\" && message.reasoning && matchingPlugin.renderReasoning) {\n          if (!showReasoning) return;\n          bubble = matchingPlugin.renderReasoning({\n            message,\n            defaultRenderer: () => createReasoningBubble(message, config),\n            config\n          });\n        } else if (message.variant === \"tool\" && message.toolCall && matchingPlugin.renderToolCall) {\n          if (!showToolCalls) return;\n          bubble = matchingPlugin.renderToolCall({\n            message,\n            defaultRenderer: () => createToolBubble(message, config),\n            config\n          });\n        } else if (matchingPlugin.renderMessage) {\n          bubble = matchingPlugin.renderMessage({\n            message,\n            defaultRenderer: () => {\n              const b = createStandardBubble(\n                message,\n                transform,\n                messageLayoutConfig,\n                config.messageActions,\n                messageActionCallbacks,\n                {\n                  loadingIndicatorRenderer: inlineLoadingRenderer,\n                  widgetConfig: config\n                }\n              );\n              if (message.role !== \"user\") {\n                enhanceWithForms(b, message, config, session);\n              }\n              return b;\n            },\n            config\n          });\n        }\n      }\n\n      // Check for component directive if no plugin handled it. We use the\n      // same stub-and-hydrate trick as ask_user_question plugins (see comment\n      // above `componentDirectiveHydrate`): build the live element with its\n      // listeners, append a stub for the morph pass, then inject the live\n      // element into the morphed wrapper afterward.\n      if (!bubble && hasDirectiveBubble) {\n        const directive = extractComponentDirectiveFromMessage(message);\n        if (directive) {\n          const lastFp = lastComponentDirectiveFingerprint.get(message.id);\n          const needsRebuild = lastFp !== fingerprint;\n          // Wrap only when the global default allows it AND the component has\n          // not opted out of bubble chrome (e.g. the artifact card carries its\n          // own border, so it renders bare).\n          const wrapChrome =\n            config.wrapComponentDirectiveInBubble !== false &&\n            componentRegistry.getOptions(directive.component)?.bubbleChrome !== false;\n          let liveBubble: HTMLElement | null = null;\n\n          if (needsRebuild) {\n            let componentBubble = renderComponentDirective(directive, {\n              config,\n              message,\n              transform\n            });\n            // Reuse a mounted inline artifact block instead of the fresh\n            // render: replacing it would cut the collapse-to-card animation\n            // short and reload live file-preview iframes.\n            if (\n              componentBubble &&\n              directive.component === \"PersonaArtifactInline\"\n            ) {\n              const fresh = componentBubble.hasAttribute(\n                \"data-artifact-inline\"\n              )\n                ? componentBubble\n                : componentBubble.querySelector<HTMLElement>(\n                    \"[data-artifact-inline]\"\n                  );\n              const artifactId =\n                fresh?.getAttribute(\"data-artifact-inline\") ?? \"\";\n              const escapedId =\n                typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\"\n                  ? CSS.escape(artifactId)\n                  : artifactId;\n              const live = artifactId\n                ? (container\n                    .querySelector<HTMLElement>(`#wrapper-${message.id}`)\n                    ?.querySelector<HTMLElement>(\n                      `[data-artifact-inline=\"${escapedId}\"]`\n                    ) ?? null)\n                : null;\n              if (\n                fresh &&\n                live &&\n                live !== fresh &&\n                hasLiveInlineArtifactBlock(live)\n              ) {\n                if (fresh === componentBubble) componentBubble = live;\n                else fresh.replaceWith(live);\n              }\n            }\n            if (componentBubble) {\n              if (wrapChrome) {\n                const componentWrapper = document.createElement(\"div\");\n                componentWrapper.className = [\n                  \"persona-message-bubble\",\n                  \"persona-max-w-[85%]\",\n                  \"persona-rounded-2xl\",\n                  \"persona-bg-persona-surface\",\n                  \"persona-border\",\n                  \"persona-border-persona-message-border\",\n                  \"persona-p-4\"\n                ].join(\" \");\n                componentWrapper.id = `bubble-${message.id}`;\n                componentWrapper.setAttribute(\"data-message-id\", message.id);\n\n                if (message.content && message.content.trim()) {\n                  const textDiv = document.createElement(\"div\");\n                  textDiv.className = \"persona-mb-3 persona-text-sm persona-leading-relaxed\";\n                  textDiv.innerHTML = transform({\n                    text: message.content,\n                    message,\n                    streaming: Boolean(message.streaming),\n                    raw: message.rawContent\n                  });\n                  componentWrapper.appendChild(textDiv);\n                }\n\n                componentWrapper.appendChild(componentBubble);\n                liveBubble = componentWrapper;\n              } else {\n                const stack = document.createElement(\"div\");\n                stack.className =\n                  \"persona-flex persona-flex-col persona-w-full persona-max-w-full persona-gap-3 persona-items-stretch\";\n                stack.id = `bubble-${message.id}`;\n                stack.setAttribute(\"data-message-id\", message.id);\n                stack.setAttribute(\"data-persona-component-directive\", \"true\");\n\n                if (message.content && message.content.trim()) {\n                  const textDiv = document.createElement(\"div\");\n                  textDiv.className =\n                    \"persona-text-sm persona-leading-relaxed persona-text-persona-primary persona-w-full\";\n                  textDiv.innerHTML = transform({\n                    text: message.content,\n                    message,\n                    streaming: Boolean(message.streaming),\n                    raw: message.rawContent\n                  });\n                  stack.appendChild(textDiv);\n                }\n\n                stack.appendChild(componentBubble);\n                liveBubble = stack;\n              }\n            }\n          }\n\n          // If the directive is registered (live bubble built or already\n          // mounted from a previous pass), use the stub-and-hydrate path.\n          // Otherwise fall through to the standard render path so the message\n          // text is at least visible.\n          if (liveBubble || lastFp != null) {\n            const stub = document.createElement(\"div\");\n            stub.className = \"persona-flex\";\n            stub.id = `wrapper-${message.id}`;\n            stub.setAttribute(\"data-wrapper-id\", message.id);\n            stub.setAttribute(\"data-component-directive-stub\", \"true\");\n            stub.setAttribute(\"data-preserve-runtime\", \"true\");\n            if (!wrapChrome) {\n              stub.classList.add(\"persona-w-full\");\n            }\n            tempContainer.appendChild(stub);\n            componentDirectiveHydrate.push({\n              messageId: message.id,\n              fingerprint,\n              bubble: liveBubble\n            });\n            return;\n          }\n        }\n      }\n\n      // Fallback to default rendering if plugin returned null or no plugin matched\n      if (!bubble) {\n        if (message.variant === \"reasoning\" && message.reasoning) {\n          if (!showReasoning) return;\n          bubble = createReasoningBubble(message, config);\n        } else if (message.variant === \"tool\" && message.toolCall) {\n          if (!showToolCalls) return;\n          bubble = createToolBubble(message, config);\n        } else if (message.variant === \"approval\" && message.approval) {\n          if (config.approval === false) return;\n          bubble = createApprovalBubble(message, config);\n        } else {\n          // Check for custom message renderers in layout config\n          const messageLayoutConfig = config.layout?.messages;\n          if (messageLayoutConfig?.renderUserMessage && message.role === \"user\") {\n            bubble = messageLayoutConfig.renderUserMessage({\n              message,\n              config,\n              streaming: Boolean(message.streaming)\n            });\n          } else if (messageLayoutConfig?.renderAssistantMessage && message.role === \"assistant\") {\n            bubble = messageLayoutConfig.renderAssistantMessage({\n              message,\n              config,\n              streaming: Boolean(message.streaming)\n            });\n          } else {\n            bubble = createStandardBubble(\n              message,\n              transform,\n              messageLayoutConfig,\n              config.messageActions,\n              messageActionCallbacks,\n              {\n                loadingIndicatorRenderer: inlineLoadingRenderer,\n                widgetConfig: config\n              }\n            );\n          }\n          if (message.role !== \"user\" && bubble) {\n            enhanceWithForms(bubble, message, config, session);\n          }\n        }\n      }\n\n      const wrapper = document.createElement(\"div\");\n      wrapper.className = \"persona-flex\";\n      // Set id for idiomorph matching\n      wrapper.id = `wrapper-${message.id}`;\n      wrapper.setAttribute(\"data-wrapper-id\", message.id);\n      if (message.role === \"user\") {\n        wrapper.classList.add(\"persona-justify-end\");\n      }\n      if (bubble?.getAttribute(\"data-persona-component-directive\") === \"true\") {\n        wrapper.classList.add(\"persona-w-full\");\n      }\n      wrapper.appendChild(bubble);\n      setCachedWrapper(messageCache, message.id, fingerprint, wrapper);\n      tempContainer.appendChild(wrapper);\n    });\n\n    // Prune any ask_user_question sheets whose source message is no longer in\n    // the message list (e.g. after clearChat or a splice).\n    if (panelElements.composerOverlay) {\n      const sheets = panelElements.composerOverlay.querySelectorAll<HTMLElement>(\n        \"[data-persona-ask-sheet-for]\"\n      );\n      sheets.forEach((sheet) => {\n        const id = sheet.getAttribute(\"data-persona-ask-sheet-for\");\n        if (id && !liveAskToolIds.has(id)) {\n          removeAskUserQuestionSheet(panelElements.composerOverlay, id);\n        }\n      });\n    }\n\n    if (config.features?.toolCallDisplay?.grouped) {\n      const toolGroups: AgentWidgetMessage[][] = [];\n      let currentGroup: AgentWidgetMessage[] = [];\n\n      messages.forEach((message) => {\n        if (message.variant === \"tool\" && message.toolCall && showToolCalls) {\n          currentGroup.push(message);\n          return;\n        }\n        // A hidden reasoning row does not create a visible break in the\n        // transcript, so it should not split an otherwise contiguous tool\n        // sequence into separate groups.\n        if (message.variant === \"reasoning\" && !showReasoning) {\n          return;\n        }\n        if (currentGroup.length > 1) {\n          toolGroups.push(currentGroup);\n        }\n        currentGroup = [];\n      });\n      if (currentGroup.length > 1) {\n        toolGroups.push(currentGroup);\n      }\n\n      toolGroups.forEach((group, groupIndex) => {\n        const wrappers = group\n          .map((groupMessage) =>\n            Array.from(tempContainer.children).find(\n              (child) =>\n                child instanceof HTMLElement &&\n                child.getAttribute(\"data-wrapper-id\") === groupMessage.id\n            ) as HTMLElement | undefined\n          )\n          .filter((wrapper): wrapper is HTMLElement => Boolean(wrapper));\n\n        if (wrappers.length < 2) {\n          return;\n        }\n\n        const groupWrapper = document.createElement(\"div\");\n        groupWrapper.className = \"persona-flex\";\n        groupWrapper.id = `wrapper-tool-group-${groupIndex}-${group[0].id}`;\n        groupWrapper.setAttribute(\"data-wrapper-id\", `tool-group-${groupIndex}-${group[0].id}`);\n\n        const groupContainer = document.createElement(\"div\");\n        groupContainer.className =\n          \"persona-tool-group persona-flex persona-w-full persona-flex-col persona-gap-2\";\n        groupContainer.setAttribute(\"data-persona-tool-group\", \"true\");\n\n        const summary = document.createElement(\"div\");\n        summary.className =\n          \"persona-tool-group-summary persona-text-xs persona-text-persona-muted\";\n\n        const defaultSummary = `Called ${group.length} tools`;\n        const renderedSummary = config.toolCall?.renderGroupedSummary?.({\n          messages: group,\n          toolCalls: group\n            .map((groupMessage) => groupMessage.toolCall)\n            .filter((toolCall): toolCall is NonNullable<typeof group[number][\"toolCall\"]> => Boolean(toolCall)),\n          defaultSummary,\n          config,\n        });\n        if (!appendRenderedValue(summary, renderedSummary)) {\n          summary.textContent = defaultSummary;\n        }\n\n        const stack = document.createElement(\"div\");\n        stack.className = \"persona-tool-group-stack persona-flex persona-flex-col\";\n\n        const summaryOnly = config.features?.toolCallDisplay?.groupedMode === \"summary\";\n        groupContainer.appendChild(summary);\n        if (!summaryOnly) {\n          groupContainer.appendChild(stack);\n        }\n        groupWrapper.appendChild(groupContainer);\n        wrappers[0].before(groupWrapper);\n\n        wrappers.forEach((wrapper, wrapperIndex) => {\n          if (summaryOnly) {\n            wrapper.remove();\n            return;\n          }\n          const item = document.createElement(\"div\");\n          item.className = \"persona-tool-group-item persona-relative\";\n          item.setAttribute(\"data-persona-tool-group-item\", \"true\");\n          if (wrapperIndex < wrappers.length - 1) {\n            item.setAttribute(\"data-persona-tool-group-connector\", \"true\");\n          }\n          item.appendChild(wrapper);\n          stack.appendChild(item);\n        });\n      });\n    }\n\n    // Remove cache entries for messages that no longer exist\n    pruneCache(messageCache, activeMessageIds);\n\n    // Add standalone typing indicator only if streaming but no assistant message is streaming yet\n    // (This shows while waiting for the stream to start)\n    // Check for ANY streaming assistant message, even if empty (to avoid duplicate bubbles)\n    const hasStreamingAssistantMessage = messages.some(\n      (msg) => msg.role === \"assistant\" && msg.streaming\n    );\n    \n    // Also check if there's a recently completed assistant message (streaming just ended)\n    // This prevents flicker when the message completes but isStreaming hasn't updated yet\n    // Approval-variant messages are UI controls, not content: exclude them so the typing\n    // indicator still shows while the agent resumes after approval\n    const lastMessage = messages[messages.length - 1];\n    const hasRecentAssistantResponse = lastMessage?.role === \"assistant\" && !lastMessage.streaming && lastMessage.variant !== \"approval\";\n\n    if (isStreaming && messages.some((msg) => msg.role === \"user\") && !hasStreamingAssistantMessage && !hasRecentAssistantResponse) {\n      // Get loading indicator using priority chain: plugin -> config -> default\n      const loadingIndicatorContext: LoadingIndicatorRenderContext = {\n        config,\n        streaming: true,\n        location: 'standalone',\n        defaultRenderer: createTypingIndicator\n      };\n\n      // Try plugin renderLoadingIndicator first\n      const loadingPlugin = plugins.find(p => p.renderLoadingIndicator);\n      let typingIndicator: HTMLElement | null = null;\n\n      if (loadingPlugin?.renderLoadingIndicator) {\n        typingIndicator = loadingPlugin.renderLoadingIndicator(loadingIndicatorContext);\n      }\n\n      // Try config loadingIndicator.render if no plugin handled it\n      if (typingIndicator === null && config.loadingIndicator?.render) {\n        typingIndicator = config.loadingIndicator.render(loadingIndicatorContext);\n      }\n\n      // Fall back to default\n      if (typingIndicator === null) {\n        typingIndicator = createTypingIndicator();\n      }\n\n      // Only render if we have an indicator (allows hiding via returning null)\n      if (typingIndicator) {\n        // Create a bubble wrapper for the typing indicator (similar to assistant messages)\n        const typingBubble = document.createElement(\"div\");\n        const showBubble = config.loadingIndicator?.showBubble !== false; // default true\n        typingBubble.className = showBubble\n          ? [\n              \"persona-max-w-[85%]\",\n              \"persona-rounded-2xl\",\n              \"persona-text-sm\",\n              \"persona-leading-relaxed\",\n              \"persona-shadow-sm\",\n              \"persona-bg-persona-surface\",\n              \"persona-border\",\n              \"persona-text-persona-primary\",\n              \"persona-px-5\",\n              \"persona-py-3\"\n            ].join(\" \")\n          : [\n              \"persona-max-w-[85%]\",\n              \"persona-text-sm\",\n              \"persona-leading-relaxed\",\n              \"persona-text-persona-primary\"\n            ].join(\" \");\n        typingBubble.setAttribute(\"data-typing-indicator\", \"true\");\n        typingBubble.style.borderColor = \"var(--persona-message-assistant-border, var(--persona-border, #e5e7eb))\";\n\n        typingBubble.appendChild(typingIndicator);\n\n        const typingWrapper = document.createElement(\"div\");\n        typingWrapper.className = \"persona-flex\";\n        // Set id for idiomorph matching\n        typingWrapper.id = \"wrapper-typing-indicator\";\n        typingWrapper.setAttribute(\"data-wrapper-id\", \"typing-indicator\");\n        typingWrapper.appendChild(typingBubble);\n        tempContainer.appendChild(typingWrapper);\n      }\n    }\n\n    // Render idle state indicator when not streaming and has messages\n    if (!isStreaming && messages.length > 0) {\n      const lastMessage = messages[messages.length - 1];\n\n      // Create context for idle indicator render functions\n      const idleIndicatorContext: IdleIndicatorRenderContext = {\n        config,\n        lastMessage,\n        messageCount: messages.length\n      };\n\n      // Get idle indicator using priority chain: plugin -> config -> null (default)\n      // Try plugin renderIdleIndicator first\n      const idlePlugin = plugins.find(p => p.renderIdleIndicator);\n      let idleIndicator: HTMLElement | null = null;\n\n      if (idlePlugin?.renderIdleIndicator) {\n        idleIndicator = idlePlugin.renderIdleIndicator(idleIndicatorContext);\n      }\n\n      // Try config loadingIndicator.renderIdle if no plugin handled it\n      if (idleIndicator === null && config.loadingIndicator?.renderIdle) {\n        idleIndicator = config.loadingIndicator.renderIdle(idleIndicatorContext);\n      }\n\n      // Only render if we have an indicator (default is null - no idle indicator)\n      if (idleIndicator) {\n        // Create a wrapper for the idle indicator (similar to typing indicator)\n        const idleBubble = document.createElement(\"div\");\n        const showBubble = config.loadingIndicator?.showBubble !== false; // default true\n        idleBubble.className = showBubble\n          ? [\n              \"persona-max-w-[85%]\",\n              \"persona-rounded-2xl\",\n              \"persona-text-sm\",\n              \"persona-leading-relaxed\",\n              \"persona-shadow-sm\",\n              \"persona-bg-persona-surface\",\n              \"persona-border\",\n              \"persona-border-persona-message-border\",\n              \"persona-text-persona-primary\",\n              \"persona-px-5\",\n              \"persona-py-3\"\n            ].join(\" \")\n          : [\n              \"persona-max-w-[85%]\",\n              \"persona-text-sm\",\n              \"persona-leading-relaxed\",\n              \"persona-text-persona-primary\"\n            ].join(\" \");\n        idleBubble.setAttribute(\"data-idle-indicator\", \"true\");\n\n        idleBubble.appendChild(idleIndicator);\n\n        const idleWrapper = document.createElement(\"div\");\n        idleWrapper.className = \"persona-flex\";\n        // Set id for idiomorph matching\n        idleWrapper.id = \"wrapper-idle-indicator\";\n        idleWrapper.setAttribute(\"data-wrapper-id\", \"idle-indicator\");\n        idleWrapper.appendChild(idleBubble);\n        tempContainer.appendChild(idleWrapper);\n      }\n    }\n\n    // Use idiomorph to morph the container contents\n    morphMessages(container, tempContainer);\n\n    // Hydrate plugin-rendered ask-question bubbles into their stub wrappers.\n    // Idiomorph imports new nodes via `document.importNode`, which strips\n    // listeners, so we built only an empty stub during morph and now inject\n    // the real, listener-bearing bubble directly into the live DOM.\n    if (askPluginHydrate.length > 0) {\n      for (const { messageId, fingerprint, bubble } of askPluginHydrate) {\n        const wrapper = container.querySelector(`#wrapper-${messageId}`);\n        if (!wrapper) continue;\n        if (bubble === null) {\n          // No fresh bubble built this pass: either the plugin opted out\n          // and a previously-mounted bubble already lives here (preserved by\n          // `data-preserve-runtime`), or we skipped the rebuild because the\n          // fingerprint matched. Either way, leave the live wrapper alone.\n          continue;\n        }\n        wrapper.replaceChildren(bubble);\n        wrapper.setAttribute(\"data-bubble-fp\", fingerprint);\n        lastAskBubbleFingerprint.set(messageId, fingerprint);\n      }\n    }\n\n    // Drop fingerprints for messages that are no longer present so a future\n    // re-appearance triggers a fresh plugin render.\n    if (lastAskBubbleFingerprint.size > 0) {\n      for (const id of lastAskBubbleFingerprint.keys()) {\n        if (!activeMessageIds.has(id)) lastAskBubbleFingerprint.delete(id);\n      }\n    }\n\n    // Hydrate component-directive bubbles into their stub wrappers, mirroring\n    // the ask-question hydration above.\n    if (componentDirectiveHydrate.length > 0) {\n      for (const { messageId, fingerprint, bubble } of componentDirectiveHydrate) {\n        const wrapper = container.querySelector(`#wrapper-${messageId}`);\n        if (!wrapper) continue;\n        if (bubble === null) {\n          // Fingerprint matched the previous pass: the live wrapper (kept\n          // alive by `data-preserve-runtime`) still holds the listener-bearing\n          // bubble from a prior render. Leave it untouched.\n          continue;\n        }\n        wrapper.replaceChildren(bubble);\n        wrapper.setAttribute(\"data-bubble-fp\", fingerprint);\n        lastComponentDirectiveFingerprint.set(messageId, fingerprint);\n      }\n    }\n\n    if (lastComponentDirectiveFingerprint.size > 0) {\n      for (const id of lastComponentDirectiveFingerprint.keys()) {\n        if (!activeMessageIds.has(id)) lastComponentDirectiveFingerprint.delete(id);\n      }\n    }\n\n    // Hydrate plugin-rendered approval bubbles into their stub wrappers,\n    // mirroring the ask-question / component-directive hydration above.\n    if (approvalPluginHydrate.length > 0) {\n      for (const { messageId, fingerprint, bubble } of approvalPluginHydrate) {\n        const wrapper = container.querySelector(`#wrapper-${messageId}`);\n        if (!wrapper) continue;\n        if (bubble === null) {\n          // Fingerprint matched the previous pass (or the plugin opted out\n          // after a prior render): the live wrapper, kept alive by\n          // `data-preserve-runtime`, still holds the listener-bearing bubble.\n          continue;\n        }\n        wrapper.replaceChildren(bubble);\n        wrapper.setAttribute(\"data-bubble-fp\", fingerprint);\n        lastApprovalBubbleFingerprint.set(messageId, fingerprint);\n      }\n    }\n\n    if (lastApprovalBubbleFingerprint.size > 0) {\n      for (const id of lastApprovalBubbleFingerprint.keys()) {\n        if (!activeMessageIds.has(id)) lastApprovalBubbleFingerprint.delete(id);\n      }\n    }\n  };\n\n  // Alias for clarity - the implementation handles flicker prevention via typing indicator logic.\n  // Re-apply read-aloud button state after each render so a playing/paused\n  // message keeps its icon across idiomorph DOM morphs.\n  const renderMessagesWithPlugins = (\n    container: HTMLElement,\n    messages: AgentWidgetMessage[],\n    transform: MessageTransform\n  ) => {\n    renderMessagesWithPluginsImpl(container, messages, transform);\n    refreshReadAloudButtons();\n  };\n\n  /**\n   * Composer-bar outside-click dismiss. While the chat is expanded, clicking\n   * anywhere outside the wrapper (i.e. NOT inside the chat panel chrome and\n   * NOT inside the pill) collapses back to just the pill. Uses `pointerdown`\n   * + capture so we run before host-page click handlers (and before any\n   * stop-propagation upstream); composedPath() includes the shadow DOM\n   * subtree, so clicks inside the wrapper (which lives in the shadow root)\n   * are correctly identified as inside.\n   */\n  let composerBarOutsideClickListener: ((e: PointerEvent) => void) | null = null;\n\n  const attachComposerBarOutsideClickDismiss = () => {\n    if (composerBarOutsideClickListener) return;\n    const listener: (e: PointerEvent) => void = (event) => {\n      const path = event.composedPath();\n      // pillRoot is a viewport-fixed sibling of the wrapper, so a click on\n      // the pill or peek wouldn't be in `wrapper`'s composedPath even\n      // though it's logically \"inside\" the widget.\n      if (path.includes(wrapper)) return;\n      if (pillRoot && path.includes(pillRoot)) return;\n      setOpenState(false, \"user\");\n    };\n    composerBarOutsideClickListener = listener;\n    const targetDoc = mount.ownerDocument ?? document;\n    targetDoc.addEventListener(\"pointerdown\", listener, true);\n  };\n\n  const detachComposerBarOutsideClickDismiss = () => {\n    if (!composerBarOutsideClickListener) return;\n    const targetDoc = mount.ownerDocument ?? document;\n    targetDoc.removeEventListener(\n      \"pointerdown\",\n      composerBarOutsideClickListener,\n      true\n    );\n    composerBarOutsideClickListener = null;\n  };\n\n  destroyCallbacks.push(() => detachComposerBarOutsideClickDismiss());\n\n  /**\n   * Composer-bar ESC dismiss. While the chat is expanded, pressing Escape\n   * collapses back to just the pill: same end state as outside-click.\n   * Matches the WAI-ARIA dialog pattern (modal mode is literally a dialog)\n   * and the dominant chat-widget convention (Intercom, Drift, Crisp).\n   * Guards on `event.isComposing` so dismissing an IME suggestion doesn't\n   * also collapse the panel.\n   */\n  let composerBarEscapeListener: ((e: KeyboardEvent) => void) | null = null;\n\n  const attachComposerBarEscapeDismiss = () => {\n    if (composerBarEscapeListener) return;\n    const listener: (e: KeyboardEvent) => void = (event) => {\n      if (event.key !== \"Escape\") return;\n      if (event.isComposing) return;\n      setOpenState(false, \"user\");\n    };\n    composerBarEscapeListener = listener;\n    const targetDoc = mount.ownerDocument ?? document;\n    targetDoc.addEventListener(\"keydown\", listener, true);\n  };\n\n  const detachComposerBarEscapeDismiss = () => {\n    if (!composerBarEscapeListener) return;\n    const targetDoc = mount.ownerDocument ?? document;\n    targetDoc.removeEventListener(\n      \"keydown\",\n      composerBarEscapeListener,\n      true\n    );\n    composerBarEscapeListener = null;\n  };\n\n  destroyCallbacks.push(() => detachComposerBarEscapeDismiss());\n\n  /**\n   * Composer-bar \"peek\" affordance: a chrome-less row above the pill that\n   * shows a chat-bubble icon, the trailing 100 chars of the most recent\n   * assistant message, and a chevron-up. It is the user's path back into the\n   * expanded chat from the collapsed pill.\n   *\n   * Visible when (collapsed) AND (there is an assistant message with content)\n   * AND (`isStreaming` OR `composerHovered`). Otherwise hidden. The hover\n   * zone is the whole `panel` (not just the pill) so the cursor moving\n   * between the pill and the peek doesn't trigger fade-out.\n   *\n   * Driven from a single `syncComposerBarPeek()` invoked from\n   * `onMessagesChanged`, `onStreamingChanged`, `updateOpenState`, the\n   * pointerenter/pointerleave on `panel`, and once at end-of-init.\n   */\n  let composerHovered = false;\n  // Track which peek-plugins we've already attached for this widget root.\n  // `ensurePluginActive` is idempotent, but the call is guarded behind a flag\n  // so we don't pay the lookup cost on every chunk.\n  const peekActivatedPlugins = new Set<string>();\n\n  /**\n   * Resolve the effective stream animation feature for the peek surface.\n   * `composerBar.peek.streamAnimation` overrides; otherwise the peek inherits\n   * `features.streamAnimation` so the surface for devs is consistent across\n   * the main bubble and the peek banner.\n   */\n  const resolvePeekStreamAnimationFeature = () => {\n    const peekFeature = config.launcher?.composerBar?.peek?.streamAnimation;\n    if (peekFeature) return peekFeature;\n    return config.features?.streamAnimation;\n  };\n\n  const syncComposerBarPeek = () => {\n    if (!isComposerBar()) return;\n    const peekBanner = panelElements.peekBanner;\n    const peekTextNode = panelElements.peekTextNode;\n    if (!peekBanner || !peekTextNode) return;\n\n    if (open) {\n      peekBanner.classList.remove(\"persona-pill-peek--visible\");\n      return;\n    }\n\n    const messages = session?.getMessages() ?? [];\n    let lastAssistant: AgentWidgetMessage | undefined;\n    for (let i = messages.length - 1; i >= 0; i--) {\n      const m = messages[i];\n      if (m.role === \"assistant\" && m.content) {\n        lastAssistant = m;\n        break;\n      }\n    }\n    if (!lastAssistant) {\n      peekBanner.classList.remove(\"persona-pill-peek--visible\");\n      return;\n    }\n\n    const text = lastAssistant.content;\n    const streaming = Boolean(lastAssistant.streaming);\n\n    // Resolve the same animation surface used by the main bubble. The peek\n    // ignores `bubbleClass` (carve-out: peek has no bubble) but honors\n    // `containerClass`, `wrap`, `useCaret`, `buffer`, `placeholder`,\n    // `speed`/`duration`, and custom plugins.\n    const feature = resolvePeekStreamAnimationFeature();\n    const streamAnimation = resolveStreamAnimation(feature);\n    const plugin =\n      streamAnimation.type !== \"none\"\n        ? resolveStreamAnimationPlugin(streamAnimation.type, feature?.plugins)\n        : null;\n    const pluginStillAnimating =\n      plugin?.isAnimating?.(lastAssistant) === true;\n    const animationActive =\n      plugin !== null && (streaming || pluginStillAnimating);\n\n    if (animationActive && plugin && !peekActivatedPlugins.has(plugin.name)) {\n      ensurePluginActive(plugin, mount);\n      peekActivatedPlugins.add(plugin.name);\n    }\n\n    // Manage `containerClass` on the peek text node. We track which class is\n    // currently applied so a config swap (or animation deactivating after\n    // stream completion) cleans up the previous class instead of stacking.\n    const desiredContainerClass =\n      animationActive && plugin?.containerClass ? plugin.containerClass : null;\n    const currentContainerClass =\n      peekTextNode.dataset.personaPeekStreamClass ?? null;\n    if (currentContainerClass && currentContainerClass !== desiredContainerClass) {\n      peekTextNode.classList.remove(currentContainerClass);\n      delete peekTextNode.dataset.personaPeekStreamClass;\n    }\n    if (desiredContainerClass && currentContainerClass !== desiredContainerClass) {\n      peekTextNode.classList.add(desiredContainerClass);\n      peekTextNode.dataset.personaPeekStreamClass = desiredContainerClass;\n    }\n\n    if (animationActive) {\n      peekTextNode.style.setProperty(\n        \"--persona-stream-step\",\n        `${streamAnimation.speed}ms`\n      );\n      peekTextNode.style.setProperty(\n        \"--persona-stream-duration\",\n        `${streamAnimation.duration}ms`\n      );\n    } else {\n      peekTextNode.style.removeProperty(\"--persona-stream-step\");\n      peekTextNode.style.removeProperty(\"--persona-stream-duration\");\n    }\n\n    // Apply buffering (word/line/plugin custom). If the buffer trims content\n    // to empty AND the placeholder is \"skeleton\", show the skeleton: that's\n    // the \"line buffer between completions\" affordance. Otherwise no\n    // pre-content placeholder on the peek (a typing-dots indicator inside a\n    // 1-line ticker would feel cramped).\n    const buffered = animationActive\n      ? applyStreamBuffer(text, streamAnimation.buffer, plugin, lastAssistant, streaming)\n      : text;\n\n    const skeletonEnabled =\n      animationActive && streamAnimation.placeholder === \"skeleton\";\n    const showSkeletonOnly =\n      skeletonEnabled && streaming && (!buffered || !buffered.trim());\n\n    if (showSkeletonOnly) {\n      // Replace text node contents with just a peek-sized skeleton bar. The\n      // bar carries `data-preserve-animation` so idiomorph keeps its shimmer\n      // running across morph passes.\n      const tempContainer = document.createElement(\"div\");\n      const skeleton = createSkeletonPlaceholder();\n      skeleton.classList.add(\"persona-pill-peek__skeleton\");\n      tempContainer.appendChild(skeleton);\n      morphMessages(peekTextNode, tempContainer);\n    } else {\n      // Trailing 100 chars; for animated modes we keep the slice but use\n      // ABSOLUTE indices so per-char/per-word span IDs stay stable as the\n      // window shifts each chunk: idiomorph then preserves animations on\n      // already-revealed units instead of restarting them. Plain \"none\" mode\n      // keeps the legacy `…` ellipsis prefix for visual continuity with the\n      // pre-animation behavior.\n      const sliceStart = Math.max(0, buffered.length - 100);\n      const slice = buffered.length > 100 ? buffered.slice(-100) : buffered;\n      const escaped = escapeHtml(slice);\n\n      if (!animationActive || !plugin) {\n        const preview = buffered.length > 100 ? `…${slice}` : slice;\n        if (peekTextNode.textContent !== preview) {\n          peekTextNode.textContent = preview;\n        }\n      } else {\n        let html = escaped;\n        if (plugin.wrap === \"char\" || plugin.wrap === \"word\") {\n          html = wrapStreamAnimation(\n            escaped,\n            plugin.wrap,\n            // Namespace span IDs to the peek surface so they don't collide\n            // with the main bubble's spans for the same message id.\n            `peek-${lastAssistant.id}`,\n            { skipTags: plugin.skipTags, startIndex: sliceStart }\n          );\n        }\n\n        const tempContainer = document.createElement(\"div\");\n        tempContainer.innerHTML = html;\n\n        if (plugin.useCaret && slice.length > 0) {\n          const caret = createStreamCaret();\n          const spans = tempContainer.querySelectorAll(\n            \".persona-stream-char, .persona-stream-word\"\n          );\n          const lastSpan = spans[spans.length - 1];\n          if (lastSpan?.parentNode) {\n            lastSpan.parentNode.insertBefore(caret, lastSpan.nextSibling);\n          } else {\n            tempContainer.appendChild(caret);\n          }\n        }\n\n        morphMessages(peekTextNode, tempContainer);\n\n        // Fire the plugin's per-render hook so glyph-cycle / wipe / custom\n        // plugins get a chance to mutate the peek's spans the same way they\n        // mutate the main bubble's. The carve-out: `bubble` here is the peek\n        // banner root, not a message bubble: plugins that target\n        // `bubbleClass` should no-op on that surface.\n        plugin.onAfterRender?.({\n          container: peekTextNode,\n          bubble: peekBanner,\n          messageId: lastAssistant.id,\n          message: lastAssistant,\n          speed: streamAnimation.speed,\n          duration: streamAnimation.duration,\n        });\n      }\n    }\n\n    const shouldShow = isStreaming || composerHovered;\n    peekBanner.classList.toggle(\"persona-pill-peek--visible\", shouldShow);\n  };\n\n  if (isComposerBar()) {\n    const peekBanner = panelElements.peekBanner;\n    if (peekBanner) {\n      // pointerdown (not click) so this competes correctly with the\n      // outside-click listener (also pointerdown, capture phase). The\n      // outside-click composedPath check passes for events inside `wrapper`\n      // or `pillRoot` (peek's parent), so the peek can stop propagation\n      // here without breaking dismissal.\n      const onPeekPointerDown = (e: PointerEvent) => {\n        e.preventDefault();\n        e.stopPropagation();\n        setOpenState(true, \"user\");\n      };\n      peekBanner.addEventListener(\"pointerdown\", onPeekPointerDown);\n      destroyCallbacks.push(() => {\n        peekBanner.removeEventListener(\"pointerdown\", onPeekPointerDown);\n      });\n    }\n\n    const onPanelPointerEnter = () => {\n      if (composerHovered) return;\n      composerHovered = true;\n      syncComposerBarPeek();\n    };\n    const onPanelPointerLeave = () => {\n      if (!composerHovered) return;\n      composerHovered = false;\n      syncComposerBarPeek();\n    };\n    panel.addEventListener(\"pointerenter\", onPanelPointerEnter);\n    panel.addEventListener(\"pointerleave\", onPanelPointerLeave);\n    destroyCallbacks.push(() => {\n      panel.removeEventListener(\"pointerenter\", onPanelPointerEnter);\n      panel.removeEventListener(\"pointerleave\", onPanelPointerLeave);\n    });\n\n    // pillRoot now hosts the pill + peek as viewport-level siblings, so the\n    // panel's pointerenter/leave above no longer fires when the cursor is\n    // over the pill area. Mirror the handlers onto pillRoot so hovering\n    // either surface still drives `composerHovered`. Both handlers are\n    // idempotent against the shared flag, so cross-traffic between panel\n    // and pillRoot doesn't cause spurious flips.\n    if (pillRoot) {\n      pillRoot.addEventListener(\"pointerenter\", onPanelPointerEnter);\n      pillRoot.addEventListener(\"pointerleave\", onPanelPointerLeave);\n      destroyCallbacks.push(() => {\n        pillRoot.removeEventListener(\"pointerenter\", onPanelPointerEnter);\n        pillRoot.removeEventListener(\"pointerleave\", onPanelPointerLeave);\n      });\n    }\n  }\n\n  /**\n   * Composer-bar geometry, owned in one place so collapsed → expanded (and\n   * back) transitions don't leave stale inline styles from a previous state.\n   * `createWrapper` no longer sets any geometry; everything flows through\n   * here.\n   *\n   * Width is expressed as `width: <configured>; max-width: calc(100vw -\n   * 32px)`. The two combine such that `width` wins on wide viewports and\n   * `max-width` clamps on narrow ones: same effect as `min(...)` but\n   * jsdom-compatible. `100vw` is always the viewport, so the containing-\n   * block edge case (host with `transform`/`filter` causing `100%` to\n   * resolve against the host instead of the viewport) is neutralized.\n   */\n  const applyComposerBarGeometry = (isOpen: boolean) => {\n    const cb = config.launcher?.composerBar ?? {};\n    const expandedSize = cb.expandedSize ?? \"anchored\";\n    const bottomOffset = cb.bottomOffset ?? \"16px\";\n    // No hardcoded default: when undefined, CSS media queries provide the\n    // responsive width (90vw / 70vw / 50vw at <640 / <1024 / >=1024) on\n    // pillRoot.\n    const collapsedMaxWidth = cb.collapsedMaxWidth;\n    const expandedMaxWidth = cb.expandedMaxWidth ?? \"880px\";\n    const expandedTopOffset = cb.expandedTopOffset ?? \"5vh\";\n    const modalMaxWidth = cb.modalMaxWidth ?? \"880px\";\n    const modalMaxHeight = cb.modalMaxHeight ?? \"min(90vh, 800px)\";\n    const viewportClamp = \"calc(100vw - 32px)\";\n    // Static fallback for the pill area's height (pill + 8px gap + peek\n    // slack). Anchored mode uses this to compute the wrapper's bottom edge\n    // so the chat panel chrome doesn't overlap the pill below. Defer\n    // ResizeObserver-based dynamic sizing until we see a real misalignment.\n    const pillAreaClearance = \"var(--persona-pill-area-height, 80px)\";\n\n    // Reset everything geometry-related so each branch sets exactly what it\n    // needs. Using empty strings drops the inline declaration entirely so\n    // CSS rules can take over (relevant for fullscreen).\n    const s = wrapper.style;\n    s.left = \"\";\n    s.right = \"\";\n    s.top = \"\";\n    s.bottom = \"\";\n    s.transform = \"\";\n    s.width = \"\";\n    s.maxWidth = \"\";\n    s.height = \"\";\n    s.maxHeight = \"\";\n\n    // pillRoot owns its own geometry (bottom offset + collapsed width\n    // override). Reset and re-apply per-config every call so config edits\n    // (e.g. via the demo's mode-switch) propagate cleanly.\n    if (pillRoot) {\n      const ps = pillRoot.style;\n      ps.bottom = bottomOffset;\n      // CSS media queries handle responsive width when no override is set.\n      ps.width = collapsedMaxWidth ?? \"\";\n    }\n\n    if (!isOpen) {\n      // Collapsed: wrapper has nothing visible to render: the container\n      // inside is `display: none` (via CSS keyed on `[data-state=\"collapsed\"]`)\n      // and the pill lives in pillRoot. Leave wrapper geometry empty so it\n      // collapses to a zero-size positioning frame at the default fixed\n      // origin. The container's fade-in keyframe handles the perceptible\n      // expand animation, so there's no chrome to lose during this state.\n      return;\n    }\n\n    if (expandedSize === \"fullscreen\") {\n      // Leave inline styles cleared so the CSS rule for fullscreen takes over.\n      return;\n    }\n\n    if (expandedSize === \"modal\") {\n      s.top = \"50%\";\n      s.left = \"50%\";\n      s.transform = \"translate(-50%, -50%)\";\n      s.bottom = \"auto\";\n      s.right = \"auto\";\n      s.width = modalMaxWidth;\n      s.maxWidth = viewportClamp;\n      s.maxHeight = modalMaxHeight;\n      s.height = modalMaxHeight;\n      return;\n    }\n\n    // Default: anchored: pill stays at the viewport bottom (in pillRoot);\n    // wrapper's bottom edge clears the pill area so the chrome doesn't\n    // overlap it.\n    s.left = \"50%\";\n    s.transform = \"translateX(-50%)\";\n    s.bottom = `calc(${bottomOffset} + ${pillAreaClearance})`;\n    s.top = expandedTopOffset;\n    s.width = expandedMaxWidth;\n    s.maxWidth = viewportClamp;\n  };\n\n  const updateOpenState = () => {\n    if (!isPanelToggleable()) return;\n\n    // Composer-bar mode morphs the wrapper between collapsed pill and\n    // expanded panel via data-attrs + per-state inline geometry. The chat\n    // body and header are hidden in the collapsed state so only the\n    // composer footer remains visible in the pill.\n    if (isComposerBar()) {\n      const cb = config.launcher?.composerBar ?? {};\n      const expandedSize = cb.expandedSize ?? \"anchored\";\n      const nextState = open ? \"expanded\" : \"collapsed\";\n      wrapper.dataset.state = nextState;\n      wrapper.dataset.expandedSize = expandedSize;\n      // pillRoot mirrors wrapper's state attributes so CSS rules keyed off\n      // [data-state] / [data-expanded-size] cascade to pill + peek even\n      // though they live outside the wrapper subtree.\n      if (pillRoot) {\n        pillRoot.dataset.state = nextState;\n        pillRoot.dataset.expandedSize = expandedSize;\n      }\n      wrapper.style.removeProperty(\"display\");\n      wrapper.classList.remove(\"persona-pointer-events-none\", \"persona-opacity-0\");\n      panel.classList.remove(\n        \"persona-scale-95\",\n        \"persona-opacity-0\",\n        \"persona-scale-100\",\n        \"persona-opacity-100\"\n      );\n\n      applyComposerBarGeometry(open);\n\n      // Toggle the entire container (chat chrome + body + close button) so\n      // the collapsed pill only shows the footer (which lives as a SIBLING\n      // of the container in the panel: see panel.appendChild(footer) above).\n      // The footer is always visible / interactive.\n      container.style.display = open ? \"flex\" : \"none\";\n\n      // Re-run chrome application now that data-state has flipped: collapsed\n      // clears container chrome (pill stands alone), expanded paints it via\n      // the same theme.components.panel.* contract as floating mode.\n      applyFullHeightStyles();\n\n      // Outside-click dismiss: while expanded, clicking anywhere outside the\n      // wrapper (panel chrome + pill) collapses back to just the pill.\n      if (open) {\n        attachComposerBarOutsideClickDismiss();\n        attachComposerBarEscapeDismiss();\n      } else {\n        detachComposerBarOutsideClickDismiss();\n        detachComposerBarEscapeDismiss();\n      }\n      // Peek banner is hidden when expanded (`open === true` short-circuits\n      // visibility); re-sync so collapsing back re-evaluates immediately.\n      syncComposerBarPeek();\n      return;\n    }\n\n    const dockedMode = isDockedMountMode(config);\n    const ownerWindow = mount.ownerDocument.defaultView ?? window;\n    const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;\n    const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;\n    const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;\n    const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;\n    const dockReveal = resolveDockConfig(config).reveal;\n    const dockRevealUsesTransform =\n      dockedMode && (dockReveal === \"overlay\" || dockReveal === \"push\") && !shouldGoFullscreen;\n\n    if (open) {\n      // Clear any display:none !important from a closed docked state so mobile fullscreen\n      // (display:flex !important) and dock layout can apply in recalcPanelHeight.\n      wrapper.style.removeProperty(\"display\");\n      wrapper.style.display = dockedMode ? \"flex\" : \"\";\n      wrapper.classList.remove(\"persona-pointer-events-none\", \"persona-opacity-0\");\n      panel.classList.remove(\"persona-scale-95\", \"persona-opacity-0\");\n      panel.classList.add(\"persona-scale-100\", \"persona-opacity-100\");\n      // Hide launcher button when widget is open\n      if (launcherButtonInstance) {\n        launcherButtonInstance.element.style.display = \"none\";\n      } else if (customLauncherElement) {\n        customLauncherElement.style.display = \"none\";\n      }\n    } else {\n      if (dockedMode) {\n        if (dockRevealUsesTransform) {\n          // Slide/push reveal: keep the panel painted so host-layout `transform` can animate.\n          wrapper.style.removeProperty(\"display\");\n          wrapper.style.display = \"flex\";\n          wrapper.classList.remove(\"persona-pointer-events-none\", \"persona-opacity-0\");\n          panel.classList.remove(\"persona-scale-100\", \"persona-opacity-100\", \"persona-scale-95\", \"persona-opacity-0\");\n        } else {\n          // Must beat applyFullHeightStyles() mobile shell: display:flex !important on wrapper\n          wrapper.style.setProperty(\"display\", \"none\", \"important\");\n          wrapper.classList.remove(\"persona-pointer-events-none\", \"persona-opacity-0\");\n          panel.classList.remove(\"persona-scale-100\", \"persona-opacity-100\", \"persona-scale-95\", \"persona-opacity-0\");\n        }\n      } else {\n        wrapper.style.display = \"\";\n        wrapper.classList.add(\"persona-pointer-events-none\", \"persona-opacity-0\");\n        panel.classList.remove(\"persona-scale-100\", \"persona-opacity-100\");\n        panel.classList.add(\"persona-scale-95\", \"persona-opacity-0\");\n      }\n      // Show launcher when closed, except docked mode (0px column: use controller.open()).\n      if (launcherButtonInstance) {\n        launcherButtonInstance.element.style.display = dockedMode ? \"none\" : \"\";\n      } else if (customLauncherElement) {\n        customLauncherElement.style.display = dockedMode ? \"none\" : \"\";\n      }\n    }\n  };\n\n  const setOpenState = (nextOpen: boolean, source: \"user\" | \"auto\" | \"api\" | \"system\" = \"user\") => {\n    if (!isPanelToggleable()) return;\n    if (open === nextOpen) return;\n    \n    const prevOpen = open;\n    open = nextOpen;\n    updateOpenState();\n\n    // Sync host stacking and scroll lock for viewport-covering modes\n    const isViewportCovering = (() => {\n      const sm = config.launcher?.sidebarMode ?? false;\n      const ow = mount.ownerDocument.defaultView ?? window;\n      const mf = config.launcher?.mobileFullscreen ?? true;\n      const mb = config.launcher?.mobileBreakpoint ?? 640;\n      const isMobile = ow.innerWidth <= mb;\n      const dockedMF = isDockedMountMode(config) && mf && isMobile;\n      // Composer-bar in expanded fullscreen mode covers the viewport: lock\n      // background scroll and elevate host stacking to match other\n      // viewport-covering modes (mobile fullscreen, sidebar).\n      const composerBarFS =\n        isComposerBar() &&\n        (config.launcher?.composerBar?.expandedSize ?? \"fullscreen\") === \"fullscreen\";\n      return sm || (mf && isMobile && launcherEnabled) || dockedMF || composerBarFS;\n    })();\n\n    if (open && isViewportCovering) {\n      if (!teardownHostStacking) {\n        const root = mount.getRootNode();\n        const hostEl = root instanceof ShadowRoot\n          ? (root.host as HTMLElement)\n          : mount.closest<HTMLElement>(\".persona-host\");\n        if (hostEl) {\n          teardownHostStacking = syncOverlayHostStacking(\n            hostEl,\n            config.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX\n          );\n        }\n      }\n      if (!releaseScrollLock) {\n        releaseScrollLock = acquireScrollLock(mount.ownerDocument);\n      }\n    } else if (!open) {\n      teardownHostStacking?.();\n      teardownHostStacking = null;\n      releaseScrollLock?.();\n      releaseScrollLock = null;\n    }\n\n    if (open) {\n      recalcPanelHeight();\n      // Reopen-where-left-off takes precedence when opted in (Principle 11);\n      // otherwise fall back to the historical per-mode positioning.\n      if (!restoreScrollPosition()) {\n        if (getScrollMode() === \"follow\") {\n          scheduleAutoScroll(true);\n        } else {\n          // Non-follow modes still start at the latest content when the panel\n          // opens; they just never chase it during streaming.\n          jumpToBottomInstant();\n        }\n      }\n    }\n\n    // Emit widget state events\n    const stateEvent: AgentWidgetStateEvent = {\n      open,\n      source,\n      timestamp: Date.now()\n    };\n    \n    if (open && !prevOpen) {\n      eventBus.emit(\"widget:opened\", stateEvent);\n    } else if (!open && prevOpen) {\n      eventBus.emit(\"widget:closed\", stateEvent);\n    }\n    \n    // Emit general state snapshot\n    eventBus.emit(\"widget:state\", {\n      open,\n      launcherEnabled,\n      voiceActive: voiceState.active,\n      streaming: session.isStreaming()\n    });\n  };\n\n  const setComposerDisabled = (disabled: boolean) => {\n    // The send button stays enabled while streaming: it doubles as a stop\n    // button. Ancillary controls (mic, suggestions, opt-in targets) still\n    // disable so the user can't race a send against an in-flight stream.\n    setSendButtonMode(disabled ? \"stop\" : \"send\");\n    if (micButton) {\n      micButton.disabled = disabled;\n    }\n    suggestionsManager.buttons.forEach((btn) => {\n      btn.disabled = disabled;\n    });\n    footer.dataset.personaComposerStreaming = disabled ? \"true\" : \"false\";\n    footer.querySelectorAll<HTMLElement>(\"[data-persona-composer-disable-when-streaming]\").forEach((el) => {\n      if (\n        el instanceof HTMLButtonElement ||\n        el instanceof HTMLInputElement ||\n        el instanceof HTMLTextAreaElement ||\n        el instanceof HTMLSelectElement\n      ) {\n        el.disabled = disabled;\n      }\n    });\n  };\n\n  const maybeFocusInput = () => {\n    if (voiceState.active) return;\n    if (!textarea) return;\n    textarea.focus();\n  };\n\n  eventBus.on(\"widget:opened\", () => {\n    if (config.autoFocusInput) setTimeout(() => maybeFocusInput(), 200);\n  });\n\n  const updateCopy = () => {\n    introTitle.textContent = config.copy?.welcomeTitle ?? \"Hello 👋\";\n    introSubtitle.textContent =\n      config.copy?.welcomeSubtitle ??\n      \"Ask anything about your account or products.\";\n    textarea.placeholder = config.copy?.inputPlaceholder ?? \"How can I help...\";\n\n    // Toggle welcome card visibility\n    const introCard = body.querySelector(\"[data-persona-intro-card]\") as HTMLElement | null;\n    if (introCard) {\n      const showCard = config.copy?.showWelcomeCard !== false;\n      introCard.style.display = showCard ? \"\" : \"none\";\n      if (showCard) {\n        body.classList.remove(\"persona-gap-3\");\n        body.classList.add(\"persona-gap-6\");\n      } else {\n        body.classList.remove(\"persona-gap-6\");\n        body.classList.add(\"persona-gap-3\");\n      }\n    }\n\n    // Only update send button text if NOT using icon mode. Skip while\n    // streaming so we don't stomp on the \"Stop\" label.\n    const useIcon = config.sendButton?.useIcon ?? false;\n    if (!useIcon && !session?.isStreaming()) {\n      sendButton.textContent = config.copy?.sendButtonLabel ?? \"Send\";\n    }\n\n    textarea.style.fontFamily =\n      'var(--persona-input-font-family, var(--persona-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif))';\n    textarea.style.fontWeight = \"var(--persona-input-font-weight, var(--persona-font-weight, 400))\";\n  };\n\n  // Add session ID persistence callbacks for client token mode\n  // These allow the widget to resume conversations by passing session_id to /client/init\n  if (config.clientToken) {\n    config = {\n      ...config,\n      getStoredSessionId: () => {\n        const storedId = persistentMetadata['sessionId'];\n        return typeof storedId === 'string' ? storedId : null;\n      },\n      setStoredSessionId: (sessionId: string) => {\n        updateSessionMetadata((prev) => ({\n          ...prev,\n          sessionId: sessionId,\n        }));\n      },\n    };\n  }\n\n  // Global timer for live-updating tool elapsed time spans.\n  // Runs at 100ms while any [data-tool-elapsed] span exists in the message area,\n  // auto-stops when none remain. Operates on real DOM after morph, not temp elements.\n  let toolElapsedTimerId: ReturnType<typeof setInterval> | null = null;\n  const ensureToolElapsedTimer = () => {\n    if (toolElapsedTimerId != null) return;\n    toolElapsedTimerId = setInterval(() => {\n      const spans = messagesWrapper.querySelectorAll<HTMLElement>(\"[data-tool-elapsed]\");\n      if (spans.length === 0) {\n        clearInterval(toolElapsedTimerId!);\n        toolElapsedTimerId = null;\n        return;\n      }\n      const now = Date.now();\n      spans.forEach((span) => {\n        const startedAt = Number(span.getAttribute(\"data-tool-elapsed\"));\n        if (!startedAt) return;\n        span.textContent = formatElapsedMs(now - startedAt);\n      });\n    }, 100);\n  };\n\n  const isDeepEqual = (left: unknown, right: unknown): boolean => {\n    if (Object.is(left, right)) return true;\n    if (left === null || right === null) return false;\n    if (typeof left !== \"object\" || typeof right !== \"object\") return false;\n    if (Array.isArray(left) || Array.isArray(right)) {\n      if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {\n        return false;\n      }\n      return left.every((value, index) => isDeepEqual(value, right[index]));\n    }\n    const leftRecord = left as Record<string, unknown>;\n    const rightRecord = right as Record<string, unknown>;\n    const leftKeys = Object.keys(leftRecord);\n    const rightKeys = Object.keys(rightRecord);\n    return (\n      leftKeys.length === rightKeys.length &&\n      leftKeys.every(\n        (key) =>\n          Object.prototype.hasOwnProperty.call(rightRecord, key) &&\n          isDeepEqual(leftRecord[key], rightRecord[key])\n      )\n    );\n  };\n\n  const withoutStreamingText = (message: AgentWidgetMessage) => {\n    const { content: _content, rawContent: _rawContent, llmContent: _llmContent, ...rest } = message;\n    return rest;\n  };\n\n  const isPlainStreamingAssistant = (message: AgentWidgetMessage) =>\n    message.role === \"assistant\" &&\n    message.streaming === true &&\n    !message.variant &&\n    !message.toolCall &&\n    !message.tools &&\n    !message.approval &&\n    !message.reasoning &&\n    !message.contentParts &&\n    !message.stopReason &&\n    !hasComponentDirective(message);\n\n  const isPureStreamingTextProgression = (\n    previous: AgentWidgetMessage[],\n    next: AgentWidgetMessage[],\n    candidate: { index: number; id: string }\n  ) => {\n    if (previous.length !== next.length) return false;\n    const before = previous[candidate.index];\n    const after = next[candidate.index];\n    if (!before || !after || before.id !== candidate.id || after.id !== candidate.id) {\n      return false;\n    }\n    const textChanged =\n      !Object.is(before.content, after.content) ||\n      !Object.is(before.rawContent, after.rawContent) ||\n      !Object.is(before.llmContent, after.llmContent);\n    return (\n      textChanged &&\n      isPlainStreamingAssistant(before) &&\n      isPlainStreamingAssistant(after) &&\n      isDeepEqual(withoutStreamingText(before), withoutStreamingText(after))\n    );\n  };\n\n  let lastAppliedMessages: AgentWidgetMessage[] | null = null;\n  let activeStreamingTextCandidate: { index: number; id: string } | null = null;\n  let pendingStreamingTextMessages: AgentWidgetMessage[] | null = null;\n  let streamingTextRAF: number | null = null;\n\n  const applyMessagesChanged = (messages: AgentWidgetMessage[]) => {\n    lastAppliedMessages = messages;\n    let lastUserMessage: AgentWidgetMessage | undefined;\n    let lastAssistantMessage: AgentWidgetMessage | undefined;\n    activeStreamingTextCandidate = null;\n    for (let index = messages.length - 1; index >= 0; index -= 1) {\n      const message = messages[index];\n      if (!lastUserMessage && message.role === \"user\") lastUserMessage = message;\n      if (!lastAssistantMessage && message.role === \"assistant\") {\n        lastAssistantMessage = message;\n      }\n      if (!activeStreamingTextCandidate && isPlainStreamingAssistant(message)) {\n        activeStreamingTextCandidate = { index, id: message.id };\n      }\n      if (lastUserMessage && lastAssistantMessage && activeStreamingTextCandidate) break;\n    }\n    renderMessagesWithPlugins(messagesWrapper, messages, postprocess);\n    // Freshly (re)built inline artifact blocks render from their persisted\n    // props; sync them with the live registry so a block created after the\n    // last onArtifactsState emission still shows current content.\n    updateInlineArtifactBlocks(messagesWrapper, lastArtifactsState.artifacts, {\n      suppressTransition: isStreaming,\n    });\n    ensureToolElapsedTimer();\n    renderSuggestions(messages);\n    scheduleAutoScroll(!isStreaming);\n    trackMessages(messages);\n\n    if (messages.length === 0) {\n      resetAnchorState();\n      followFallbackActive = true;\n      currentTurnAnchored = false;\n    }\n    if (!scrollSendSeeded || suppressScrollSend) {\n      scrollSendSeeded = true;\n      lastSentUserMessageId = lastUserMessage?.id ?? null;\n      lastHandledAssistantId = lastAssistantMessage?.id ?? null;\n    } else if (lastUserMessage && lastUserMessage.id !== lastSentUserMessageId) {\n      lastSentUserMessageId = lastUserMessage.id;\n      handleUserMessageSent(lastUserMessage.id);\n    } else if (\n      lastAssistantMessage &&\n      lastAssistantMessage.id !== lastHandledAssistantId\n    ) {\n      handleAssistantTurnStarted();\n    }\n    if (lastAssistantMessage) lastHandledAssistantId = lastAssistantMessage.id;\n\n    const prevLastUserMessageId = voiceState.lastUserMessageId;\n    if (lastUserMessage && lastUserMessage.id !== prevLastUserMessageId) {\n      voiceState.lastUserMessageId = lastUserMessage.id;\n      eventBus.emit(\"user:message\", lastUserMessage);\n    }\n\n    voiceState.lastUserMessageWasVoice = Boolean(lastUserMessage?.viaVoice);\n    persistState(messages);\n    syncComposerBarPeek();\n  };\n\n  const flushPendingStreamingText = () => {\n    if (streamingTextRAF !== null) {\n      cancelAnimationFrame(streamingTextRAF);\n      streamingTextRAF = null;\n    }\n    const pending = pendingStreamingTextMessages;\n    pendingStreamingTextMessages = null;\n    if (pending) applyMessagesChanged(pending);\n  };\n\n  const discardPendingStreamingText = () => {\n    if (streamingTextRAF !== null) cancelAnimationFrame(streamingTextRAF);\n    streamingTextRAF = null;\n    pendingStreamingTextMessages = null;\n  };\n\n  const handleMessagesChanged = (messages: AgentWidgetMessage[]) => {\n    const comparison = pendingStreamingTextMessages ?? lastAppliedMessages;\n    if (\n      isStreaming &&\n      comparison &&\n      activeStreamingTextCandidate &&\n      isPureStreamingTextProgression(\n        comparison,\n        messages,\n        activeStreamingTextCandidate\n      )\n    ) {\n      pendingStreamingTextMessages = messages;\n      if (streamingTextRAF === null) {\n        streamingTextRAF = requestAnimationFrame(() => {\n          streamingTextRAF = null;\n          const pending = pendingStreamingTextMessages;\n          pendingStreamingTextMessages = null;\n          if (pending) applyMessagesChanged(pending);\n        });\n      }\n      return;\n    }\n\n    if (messages.length === 0) {\n      discardPendingStreamingText();\n      applyMessagesChanged(messages);\n      return;\n    }\n    flushPendingStreamingText();\n    applyMessagesChanged(messages);\n  };\n\n  session = new AgentWidgetSession(config, {\n    onMessagesChanged(messages) {\n      handleMessagesChanged(messages);\n    },\n    onStatusChanged(status) {\n      const currentStatusConfig = config.statusIndicator ?? {};\n      const getCurrentStatusText = (s: AgentWidgetSessionStatus): string => {\n        if (s === \"idle\") return currentStatusConfig.idleText ?? statusCopy.idle;\n        if (s === \"connecting\") return currentStatusConfig.connectingText ?? statusCopy.connecting;\n        if (s === \"connected\") return currentStatusConfig.connectedText ?? statusCopy.connected;\n        if (s === \"error\") return currentStatusConfig.errorText ?? statusCopy.error;\n        if (s === \"paused\") return currentStatusConfig.pausedText ?? statusCopy.paused;\n        if (s === \"resuming\") return currentStatusConfig.resumingText ?? statusCopy.resuming;\n        return statusCopy[s];\n      };\n      applyStatusToElement(statusText, getCurrentStatusText(status), currentStatusConfig, status);\n    },\n    onStreamingChanged(streaming) {\n      if (!streaming) {\n        if (session?.getMessages().length === 0) {\n          discardPendingStreamingText();\n        } else {\n          flushPendingStreamingText();\n        }\n      }\n      isStreaming = streaming;\n      setComposerDisabled(streaming);\n      // Re-render messages to show/hide typing indicator\n      if (session) {\n        renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);\n      }\n      if (!streaming) {\n        scheduleAutoScroll(true);\n      }\n      // Keep the \"streaming below\" hint and its announcement in sync with the\n      // streaming lifecycle (Principles 8 + 15).\n      syncScrollToBottomButton();\n      announce(streaming ? \"Responding…\" : \"Response complete.\");\n      // Composer-bar peek: streaming state is one of the two visibility\n      // triggers (the other is composer hover), so re-evaluate now.\n      syncComposerBarPeek();\n    },\n    onVoiceStatusChanged(status: VoiceStatus) {\n      // Surface the granular status publicly so consumers can render their own\n      // per-state UI (e.g. a listening/speaking status dock). Fires for every\n      // provider; the mic-button styling below is runtype-specific.\n      eventBus.emit(\"voice:status\", { status, timestamp: Date.now() });\n      if (config.voiceRecognition?.provider?.type !== 'runtype') return;\n\n      switch (status) {\n        case 'listening':\n          // A continuous realtime call re-enters `listening` after every spoken\n          // reply, so reassert the recording styles here (they were replaced by\n          // the `processing`/`speaking` states during the turn). The initial\n          // listen is also styled by the toggleVoice()/startVoiceRecognition()\n          // flows; reapplying is idempotent.\n          removeRuntypeMicStateStyles();\n          applyRuntypeMicRecordingStyles();\n          break;\n        case 'processing':\n          removeRuntypeMicStateStyles();\n          applyRuntypeMicProcessingStyles();\n          break;\n        case 'speaking':\n          removeRuntypeMicStateStyles();\n          applyRuntypeMicSpeakingStyles();\n          break;\n        default:\n          // idle, connected, disconnected, error\n          if (status === 'idle' && session.isBargeInActive()) {\n            // Barge-in mic is still hot between turns: show it as active\n            removeRuntypeMicStateStyles();\n            applyRuntypeMicRecordingStyles();\n            micButton?.setAttribute(\"aria-label\", \"End voice session\");\n          } else {\n            voiceState.active = false;\n            removeRuntypeMicStateStyles();\n            emitVoiceState(\"system\");\n            persistVoiceMetadata();\n          }\n          break;\n      }\n    },\n    onArtifactsState(state) {\n      lastArtifactsState = state;\n      // A cleared registry ends any explicit-open override: the next artifact\n      // decides pane visibility purely from its own display mode.\n      if (state.artifacts.length === 0) {\n        artifactsPaneUserOpened = false;\n      }\n      // Route streaming registry updates (artifact_delta / artifact_complete)\n      // into any inline artifact blocks in the transcript. Suppress the\n      // streaming→complete View Transition while the session is still\n      // streaming: it captures the whole document, and cross-fading a stale\n      // snapshot over still-moving message text reads as ghosting/motion blur\n      // on the transcript.\n      updateInlineArtifactBlocks(messagesWrapper, state.artifacts, {\n        suppressTransition: isStreaming,\n      });\n      syncArtifactPane();\n      persistState();\n    },\n    onReconnect(event) {\n      // Map the durable-reconnect lifecycle to public controller events.\n      const { executionId, lastEventId } = event.handle;\n      if (event.phase === \"paused\") {\n        eventBus.emit(\"stream:paused\", { executionId, after: lastEventId });\n      } else if (event.phase === \"resuming\") {\n        eventBus.emit(\"stream:resuming\", {\n          executionId,\n          after: lastEventId,\n          attempt: event.attempt ?? 1,\n        });\n      } else {\n        eventBus.emit(\"stream:resumed\", { executionId, after: lastEventId });\n      }\n    }\n  });\n\n  sessionRef.current = session;\n\n  // On teardown, cancel any in-flight turn/reconnect so a pending durable\n  // reconnect's backoff timer and focus/online listeners don't outlive the\n  // widget (cancel() → teardownReconnect()).\n  destroyCallbacks.push(() => session.cancel());\n\n  // Mirror read-aloud playback state into the action buttons, and surface it as\n  // a controller event (parallel to message:copy / message:feedback).\n  let lastReadAloudId: string | null = null;\n  session.onReadAloudChange((activeId, state) => {\n    readAloudActiveId = activeId;\n    readAloudActiveState = state;\n    refreshReadAloudButtons();\n\n    // On the terminal `idle` transition activeId is null, so fall back to the\n    // last active id to identify the message that just finished/stopped.\n    const messageId = activeId ?? lastReadAloudId;\n    if (activeId) lastReadAloudId = activeId;\n    const message = messageId\n      ? session.getMessages().find((m) => m.id === messageId) ?? null\n      : null;\n    eventBus.emit(\"message:read-aloud\", {\n      messageId,\n      message,\n      state,\n      timestamp: Date.now(),\n    });\n    if (state === \"idle\") lastReadAloudId = null;\n  });\n\n  // The constructor only emits onMessagesChanged when it has initial\n  // messages, so seed send-detection explicitly for the empty-session case:  // otherwise the user's very first send would be mistaken for the seed.\n  scrollSendSeeded = true;\n\n  // Setup Runtype voice provider when configured (connects WebSocket for server-side STT)\n  if (config.voiceRecognition?.provider?.type === 'runtype') {\n    try {\n      session.setupVoice();\n    } catch (err) {\n      if (typeof console !== 'undefined') {\n        // eslint-disable-next-line no-console\n        console.warn('[AgentWidget] Runtype voice setup failed:', err);\n      }\n    }\n  }\n\n  // Pre-initialize client session when in client token mode so feedback works\n  // before the user sends their first message (e.g. on restored/persisted messages)\n  if (config.clientToken) {\n    session.initClientSession().catch((err) => {\n      if (config.debug) {\n        // eslint-disable-next-line no-console\n        console.warn(\"[AgentWidget] Pre-init client session failed:\", err);\n      }\n    });\n  }\n\n  // Wire up optional SSE tap (host) + event stream buffer to capture SSE events\n  if (eventStreamBuffer || config.onSSEEvent) {\n    session.setSSEEventCallback((type: string, payload: unknown) => {\n      config.onSSEEvent?.(type, payload);\n      throughputTracker?.processEvent(type, payload);\n      eventStreamBuffer?.push({\n        id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n        type,\n        timestamp: Date.now(),\n        payload: JSON.stringify(payload)\n      });\n    });\n  }\n\n  // Durable-session reconnect boot path: once history is in place,\n  // if the host passed a non-terminal resume handle (+ a reconnect transport),\n  // immediately re-enter `resuming` and replay everything past the cursor into\n  // the restored conversation. Fires AFTER hydration so the trailing partial\n  // assistant bubble exists for the replay to append to.\n  const maybeBootResume = () => {\n    if (config.resume && typeof config.reconnectStream === \"function\") {\n      session.resumeFromHandle(config.resume);\n    }\n  };\n\n  if (pendingStoredState) {\n    pendingStoredState\n      .then((state) => {\n        if (!state) return;\n        if (state.metadata) {\n          persistentMetadata = ensureRecord(state.metadata);\n          actionManager.syncFromMetadata();\n        }\n        if (state.messages?.length) {\n          // Restored history must not read as a fresh send (scroll-on-send /\n          // anchor-top would fire for the last restored user message).\n          suppressScrollSend = true;\n          try {\n            session.hydrateMessages(state.messages);\n          } finally {\n            suppressScrollSend = false;\n          }\n        }\n        if (state.artifacts?.length) {\n          session.hydrateArtifacts(\n            state.artifacts,\n            state.selectedArtifactId ?? null\n          );\n        }\n      })\n      .catch((error) => {\n        if (typeof console !== \"undefined\") {\n          // eslint-disable-next-line no-console\n          console.error(\"[AgentWidget] Failed to hydrate stored state:\", error);\n        }\n      })\n      .finally(() => maybeBootResume());\n  } else {\n    maybeBootResume();\n  }\n\n  // Centralized so both the default composer (`handleSubmit`) and the plugin\n  // composer (`renderComposer.onSubmit`) auto-expand the composer-bar wrapper\n  // when a message is sent while the panel is collapsed. Without a single\n  // helper the two submit paths drift over time.\n  const maybeExpandComposerBar = () => {\n    if (!isComposerBar()) return;\n    if (open) return;\n    const expandOnSubmit = config.launcher?.composerBar?.expandOnSubmit ?? true;\n    if (!expandOnSubmit) return;\n    setOpenState(true, \"auto\");\n  };\n\n  const handleSubmit = (event: Event) => {\n    event.preventDefault();\n\n    // While a response is streaming, the submit button acts as a stop button.\n    // Abort the in-flight stream and leave textarea contents / attachments\n    // intact so the user can edit and resend without retyping.\n    if (session.isStreaming()) {\n      session.cancel();\n      // Cancelling emits no terminal/error SSE frame, so reset the throughput\n      // tracker (as clear-chat does) to avoid a stale `running` row lingering.\n      throughputTracker?.reset();\n      eventStreamView?.update();\n      return;\n    }\n\n    const value = textarea.value.trim();\n    const hasAttachments = attachmentManager?.hasAttachments() ?? false;\n\n    // Must have text or attachments to send\n    if (!value && !hasAttachments) return;\n\n    maybeExpandComposerBar();\n\n    // Build content parts if there are attachments\n    let contentParts: ContentPart[] | undefined;\n    if (hasAttachments) {\n      contentParts = [];\n      // Add image parts first\n      contentParts.push(...attachmentManager!.getContentParts());\n      // Add text part if there's text\n      if (value) {\n        contentParts.push(createTextPart(value));\n      }\n    }\n\n    textarea.value = \"\";\n    textarea.style.height = \"auto\"; // Reset height after clearing\n    resetHistoryNavigation();\n\n    // Send message with optional content parts\n    session.sendMessage(value, { contentParts });\n\n    // Clear attachments after sending\n    if (hasAttachments) {\n      attachmentManager!.clearAttachments();\n    }\n  };\n\n  // --- Composer message-history navigation (Up/Down arrows) ---\n  // Lets users recall and edit previously sent messages, shell/Slack style.\n  // The pure state machine lives in utils/composer-history.ts; here we feed it\n  // caret info and apply the value it returns. Text-only recall: attachments\n  // on past messages are not restored.\n  const historyNavigationEnabled = () =>\n    config.features?.composerHistory !== false;\n\n  let composerHistoryState: ComposerHistoryState = { ...INITIAL_HISTORY_STATE };\n  // Guards the reset-on-edit listener so our own programmatic value sets (which\n  // dispatch an `input` event for auto-resize) don't exit navigation mode.\n  let suppressHistoryReset = false;\n\n  const resetHistoryNavigation = () => {\n    composerHistoryState = { ...INITIAL_HISTORY_STATE };\n  };\n\n  const getUserMessageHistory = (): string[] =>\n    session\n      .getMessages()\n      .filter((message) => message.role === \"user\")\n      .map((message) => message.content ?? \"\")\n      .filter((text) => text.length > 0);\n\n  const applyHistoryValue = (value: string) => {\n    if (!textarea) return;\n    suppressHistoryReset = true;\n    textarea.value = value;\n    // Trigger the auto-resize handler (it listens on `input`).\n    textarea.dispatchEvent(new Event(\"input\", { bubbles: true }));\n    suppressHistoryReset = false;\n    // Caret to end for natural editing / appending.\n    const end = textarea.value.length;\n    textarea.setSelectionRange(end, end);\n  };\n\n  const handleComposerInput = () => {\n    // A real edit leaves history-navigation mode.\n    if (suppressHistoryReset) return;\n    resetHistoryNavigation();\n  };\n\n  const handleComposerKeydown = (event: KeyboardEvent) => {\n    if (!textarea) return;\n\n    // Up/Down: walk through previously sent user messages.\n    if (\n      historyNavigationEnabled() &&\n      (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") &&\n      !event.shiftKey &&\n      !event.metaKey &&\n      !event.ctrlKey &&\n      !event.altKey &&\n      !event.isComposing\n    ) {\n      const atStart =\n        textarea.selectionStart === 0 && textarea.selectionEnd === 0;\n      const result = navigateComposerHistory({\n        direction: event.key === \"ArrowUp\" ? \"up\" : \"down\",\n        history: getUserMessageHistory(),\n        currentValue: textarea.value,\n        atStart,\n        state: composerHistoryState\n      });\n      composerHistoryState = result.state;\n      if (result.handled) {\n        event.preventDefault();\n        if (result.value !== undefined) {\n          applyHistoryValue(result.value);\n        }\n        return;\n      }\n      // Not handled: fall through to default cursor movement.\n    }\n\n    // Enter: send, unless a response is streaming. While streaming, Enter is\n    // inert (never a stop trigger): the visible Stop button / Esc stop it.\n    if (event.key === \"Enter\" && !event.shiftKey) {\n      if (session.isStreaming()) {\n        event.preventDefault();\n        return;\n      }\n      resetHistoryNavigation();\n      event.preventDefault();\n      sendButton.click();\n    }\n  };\n\n  // Esc-to-stop: while a response streams, Escape within this widget aborts it.\n  // Capture phase + registered at init so it runs before the composer-bar Esc\n  // collapse listener (attached later on open); stopImmediatePropagation keeps\n  // a stream-stop from also collapsing the panel. Scoped via composedPath so a\n  // page-wide Escape elsewhere doesn't hijack.\n  const handleEscStop = (event: KeyboardEvent) => {\n    if (event.key !== \"Escape\" || event.isComposing) return;\n    if (!session.isStreaming()) return;\n    if (!event.composedPath().includes(container)) return;\n    session.cancel();\n    // Cancelling emits no terminal/error SSE frame: reset throughput so the\n    // Events row doesn't keep showing a live rate from the stopped stream.\n    throughputTracker?.reset();\n    eventStreamView?.update();\n    resetHistoryNavigation();\n    event.preventDefault();\n    event.stopImmediatePropagation();\n  };\n\n  const handleInputPaste = async (event: ClipboardEvent) => {\n    if (config.attachments?.enabled !== true || !attachmentManager) return;\n\n    const clipboardImageFiles = getClipboardImageFiles(event.clipboardData);\n    if (clipboardImageFiles.length === 0) return;\n\n    // Prevent browser text/html paste when handling clipboard images as attachments.\n    event.preventDefault();\n    await attachmentManager.handleFiles(clipboardImageFiles);\n  };\n\n  // Voice recognition state and logic\n  let speechRecognition: any = null;\n  let isRecording = false;\n  let pauseTimer: number | null = null;\n  let originalMicStyles: {\n    backgroundColor: string;\n    color: string;\n    borderColor: string;\n    iconName: string;\n    iconSize: number;\n  } | null = null;\n\n  const getSpeechRecognitionClass = (): any => {\n    if (typeof window === 'undefined') return null;\n    return (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition || null;\n  };\n\n  const startVoiceRecognition = (\n    source: AgentWidgetVoiceStateEvent[\"source\"] = \"user\"\n  ) => {\n    if (isRecording || session.isStreaming()) return;\n\n    const SpeechRecognitionClass = getSpeechRecognitionClass();\n    if (!SpeechRecognitionClass) return;\n\n    speechRecognition = new SpeechRecognitionClass();\n    const voiceConfig = config.voiceRecognition ?? {};\n    const pauseDuration = voiceConfig.pauseDuration ?? 2000;\n\n    speechRecognition.continuous = true;\n    speechRecognition.interimResults = true;\n    speechRecognition.lang = 'en-US';\n\n    // Store the initial text that was in the textarea\n    const initialText = textarea.value;\n\n    speechRecognition.onresult = (event: any) => {\n      // Build the complete transcript from all results\n      let fullTranscript = \"\";\n      let interimTranscript = \"\";\n      \n      // Process all results from the beginning\n      for (let i = 0; i < event.results.length; i++) {\n        const result = event.results[i];\n        const transcript = result[0].transcript;\n        \n        if (result.isFinal) {\n          fullTranscript += transcript + \" \";\n        } else {\n          // Only take the last interim result\n          interimTranscript = transcript;\n        }\n      }\n      \n      // Update textarea with initial text + full transcript + interim\n      const newValue = initialText + fullTranscript + interimTranscript;\n      textarea.value = newValue;\n\n      // Reset pause timer on each result\n      if (pauseTimer) {\n        clearTimeout(pauseTimer);\n      }\n\n      // Set timer to auto-submit after pause when we have any speech\n      if (fullTranscript || interimTranscript) {\n        pauseTimer = window.setTimeout(() => {\n          const finalValue = textarea.value.trim();\n          if (finalValue && speechRecognition && isRecording) {\n            stopVoiceRecognition();\n            textarea.value = \"\";\n            textarea.style.height = \"auto\"; // Reset height after clearing\n            session.sendMessage(finalValue, { viaVoice: true });\n          }\n        }, pauseDuration);\n      }\n    };\n\n    speechRecognition.onerror = (event: any) => {\n      // Don't stop on \"no-speech\" error, just ignore it\n      if (event.error !== 'no-speech') {\n        stopVoiceRecognition();\n      }\n    };\n\n    speechRecognition.onend = () => {\n      // If recognition ended naturally (not manually stopped), submit if there's text\n      if (isRecording) {\n        const finalValue = textarea.value.trim();\n        if (finalValue && finalValue !== initialText.trim()) {\n          textarea.value = \"\";\n          textarea.style.height = \"auto\"; // Reset height after clearing\n          session.sendMessage(finalValue, { viaVoice: true });\n        }\n        stopVoiceRecognition();\n      }\n    };\n\n    try {\n      speechRecognition.start();\n      isRecording = true;\n      voiceState.active = true;\n      if (source !== \"system\") {\n        voiceState.manuallyDeactivated = false;\n      }\n      emitVoiceState(source);\n      persistVoiceMetadata();\n      if (micButton) {\n        // Store original styles (including icon info for restoration)\n        const voiceConfig = config.voiceRecognition ?? {};\n        originalMicStyles = {\n          backgroundColor: micButton.style.backgroundColor,\n          color: micButton.style.color,\n          borderColor: micButton.style.borderColor,\n          iconName: voiceConfig.iconName ?? \"mic\",\n          iconSize: parseFloat(voiceConfig.iconSize ?? config.sendButton?.size ?? \"40\") || 24,\n        };\n\n        // Apply recording state styles from config or theme tokens\n        const recordingBackgroundColor = voiceConfig.recordingBackgroundColor;\n        const recordingIconColor = voiceConfig.recordingIconColor;\n        const recordingBorderColor = voiceConfig.recordingBorderColor;\n\n        micButton.classList.add(\"persona-voice-recording\");\n        micButton.style.backgroundColor = recordingBackgroundColor ?? \"var(--persona-voice-recording-bg, #ef4444)\";\n        micButton.style.color = recordingIconColor ?? \"var(--persona-voice-recording-indicator, #ffffff)\";\n\n        if (recordingIconColor) {\n          const svg = micButton.querySelector(\"svg\");\n          if (svg) {\n            svg.setAttribute(\"stroke\", recordingIconColor);\n          }\n        }\n        \n        if (recordingBorderColor) {\n          micButton.style.borderColor = recordingBorderColor;\n        }\n        \n        micButton.setAttribute(\"aria-label\", \"Stop voice recognition\");\n      }\n    } catch (error) {\n      stopVoiceRecognition(\"system\");\n    }\n  };\n\n  const stopVoiceRecognition = (\n    source: AgentWidgetVoiceStateEvent[\"source\"] = \"user\"\n  ) => {\n    if (!isRecording) return;\n\n    isRecording = false;\n    if (pauseTimer) {\n      clearTimeout(pauseTimer);\n      pauseTimer = null;\n    }\n\n    if (speechRecognition) {\n      try {\n        speechRecognition.stop();\n      } catch (error) {\n        // Ignore errors when stopping\n      }\n      speechRecognition = null;\n    }\n\n    voiceState.active = false;\n    emitVoiceState(source);\n    persistVoiceMetadata();\n\n    if (micButton) {\n      micButton.classList.remove(\"persona-voice-recording\");\n      \n      // Restore original styles\n      if (originalMicStyles) {\n        micButton.style.backgroundColor = originalMicStyles.backgroundColor;\n        micButton.style.color = originalMicStyles.color;\n        micButton.style.borderColor = originalMicStyles.borderColor;\n        \n        // Restore SVG stroke color if present\n        const svg = micButton.querySelector(\"svg\");\n        if (svg) {\n          svg.setAttribute(\"stroke\", originalMicStyles.color || \"currentColor\");\n        }\n        \n        originalMicStyles = null;\n      }\n      \n      micButton.setAttribute(\"aria-label\", \"Start voice recognition\");\n    }\n  };\n\n  // Function to create mic button dynamically\n  const createMicButton = (voiceConfig: AgentWidgetConfig['voiceRecognition'], sendButtonConfig: AgentWidgetConfig['sendButton']): { micButton: HTMLButtonElement; micButtonWrapper: HTMLElement } | null => {\n    const hasSpeechRecognition =\n      typeof window !== 'undefined' &&\n      (typeof (window as any).webkitSpeechRecognition !== 'undefined' ||\n       typeof (window as any).SpeechRecognition !== 'undefined');\n    const hasRuntypeProvider = voiceConfig?.provider?.type === 'runtype';\n    // Bring-your-own (`custom`) providers own their own input pipeline (cloud\n    // STT, etc.), so the mic should render regardless of Web Speech support.\n    const hasCustomProvider = voiceConfig?.provider?.type === 'custom';\n    const hasVoiceInput = hasSpeechRecognition || hasRuntypeProvider || hasCustomProvider;\n\n    if (!hasVoiceInput) return null;\n\n    const micButtonWrapper = createElement(\"div\", \"persona-send-button-wrapper\");\n    const micButton = createElement(\n      \"button\",\n      \"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer\"\n    ) as HTMLButtonElement;\n    \n    micButton.type = \"button\";\n    micButton.setAttribute(\"aria-label\", \"Start voice recognition\");\n    \n    const micIconName = voiceConfig?.iconName ?? \"mic\";\n    const buttonSize = sendButtonConfig?.size ?? \"40px\";\n    const micIconSize = voiceConfig?.iconSize ?? buttonSize;\n    const micIconSizeNum = parseFloat(micIconSize) || 24;\n    \n    // Use dedicated colors from voice recognition config, fallback to send button colors\n    const backgroundColor = voiceConfig?.backgroundColor ?? sendButtonConfig?.backgroundColor;\n    const iconColor = voiceConfig?.iconColor ?? sendButtonConfig?.textColor;\n    \n    micButton.style.width = micIconSize;\n    micButton.style.height = micIconSize;\n    micButton.style.minWidth = micIconSize;\n    micButton.style.minHeight = micIconSize;\n    micButton.style.fontSize = \"18px\";\n    micButton.style.lineHeight = \"1\";\n    \n    // Set mic button foreground from config or theme token\n    if (iconColor) {\n      micButton.style.color = iconColor;\n    } else {\n      micButton.style.color = \"var(--persona-text, #111827)\";\n    }\n\n    // Use Lucide mic icon (stroke width 1.5 for minimalist outline style)\n    const iconColorValue = iconColor || \"currentColor\";\n    const micIconSvg = renderLucideIcon(micIconName, micIconSizeNum, iconColorValue, 1.5);\n    if (micIconSvg) {\n      micButton.appendChild(micIconSvg);\n    } else {\n      micButton.textContent = \"🎤\";\n    }\n\n    // Apply background color\n    if (backgroundColor) {\n      micButton.style.backgroundColor = backgroundColor;\n    } else {\n      micButton.style.backgroundColor = \"\";\n    }\n    \n    // Apply border styling\n    if (voiceConfig?.borderWidth) {\n      micButton.style.borderWidth = voiceConfig.borderWidth;\n      micButton.style.borderStyle = \"solid\";\n    }\n    if (voiceConfig?.borderColor) {\n      micButton.style.borderColor = voiceConfig.borderColor;\n    }\n    \n    // Apply padding styling\n    if (voiceConfig?.paddingX) {\n      micButton.style.paddingLeft = voiceConfig.paddingX;\n      micButton.style.paddingRight = voiceConfig.paddingX;\n    }\n    if (voiceConfig?.paddingY) {\n      micButton.style.paddingTop = voiceConfig.paddingY;\n      micButton.style.paddingBottom = voiceConfig.paddingY;\n    }\n    \n    micButtonWrapper.appendChild(micButton);\n    \n    // Add tooltip if enabled\n    const tooltipText = voiceConfig?.tooltipText ?? \"Start voice recognition\";\n    const showTooltip = voiceConfig?.showTooltip ?? false;\n    if (showTooltip && tooltipText) {\n      const tooltip = createElement(\"div\", \"persona-send-button-tooltip\");\n      tooltip.textContent = tooltipText;\n      micButtonWrapper.appendChild(tooltip);\n    }\n    \n    return { micButton, micButtonWrapper };\n  };\n\n  // --- Helpers to store/restore original mic button state ---\n\n  const storeOriginalMicStyles = () => {\n    if (!micButton || originalMicStyles) return; // Already stored\n    const voiceConfig = config.voiceRecognition ?? {};\n    originalMicStyles = {\n      backgroundColor: micButton.style.backgroundColor,\n      color: micButton.style.color,\n      borderColor: micButton.style.borderColor,\n      iconName: voiceConfig.iconName ?? \"mic\",\n      iconSize: parseFloat(voiceConfig.iconSize ?? config.sendButton?.size ?? \"40\") || 24,\n    };\n  };\n\n  /** Swap the mic button's SVG icon */\n  const swapMicIcon = (iconName: string, color: string) => {\n    if (!micButton) return;\n    const existingSvg = micButton.querySelector(\"svg\");\n    if (existingSvg) existingSvg.remove();\n    const size = originalMicStyles?.iconSize ?? (parseFloat(config.voiceRecognition?.iconSize ?? config.sendButton?.size ?? \"40\") || 24);\n    const newSvg = renderLucideIcon(iconName, size, color, 1.5);\n    if (newSvg) micButton.appendChild(newSvg);\n  };\n\n  /** Remove all voice state CSS classes */\n  const removeAllVoiceStateClasses = () => {\n    if (!micButton) return;\n    micButton.classList.remove(\"persona-voice-recording\", \"persona-voice-processing\", \"persona-voice-speaking\");\n  };\n\n  // --- Per-state style application ---\n\n  const applyRuntypeMicRecordingStyles = () => {\n    if (!micButton) return;\n    storeOriginalMicStyles();\n    const voiceConfig = config.voiceRecognition ?? {};\n    const recordingBackgroundColor = voiceConfig.recordingBackgroundColor;\n    const recordingIconColor = voiceConfig.recordingIconColor;\n    const recordingBorderColor = voiceConfig.recordingBorderColor;\n    removeAllVoiceStateClasses();\n    micButton.classList.add(\"persona-voice-recording\");\n    micButton.style.backgroundColor = recordingBackgroundColor ?? \"var(--persona-voice-recording-bg, #ef4444)\";\n    micButton.style.color = recordingIconColor ?? \"var(--persona-voice-recording-indicator, #ffffff)\";\n    if (recordingIconColor) {\n      const svg = micButton.querySelector(\"svg\");\n      if (svg) svg.setAttribute(\"stroke\", recordingIconColor);\n    }\n    if (recordingBorderColor) micButton.style.borderColor = recordingBorderColor;\n    micButton.setAttribute(\"aria-label\", \"Stop voice recognition\");\n  };\n\n  const applyRuntypeMicProcessingStyles = () => {\n    if (!micButton) return;\n    storeOriginalMicStyles();\n    const voiceConfig = config.voiceRecognition ?? {};\n    const interruptionMode = session.getVoiceInterruptionMode();\n    const iconName = voiceConfig.processingIconName ?? \"loader\";\n    const iconColor = voiceConfig.processingIconColor ?? originalMicStyles?.color ?? \"\";\n    const bgColor = voiceConfig.processingBackgroundColor ?? originalMicStyles?.backgroundColor ?? \"\";\n    const borderColor = voiceConfig.processingBorderColor ?? originalMicStyles?.borderColor ?? \"\";\n\n    removeAllVoiceStateClasses();\n    micButton.classList.add(\"persona-voice-processing\");\n    micButton.style.backgroundColor = bgColor;\n    micButton.style.borderColor = borderColor;\n    const resolvedColor = iconColor || \"currentColor\";\n    micButton.style.color = resolvedColor;\n    swapMicIcon(iconName, resolvedColor);\n    micButton.setAttribute(\"aria-label\", \"Processing voice input\");\n    // In \"none\" mode the button is not actionable during processing\n    if (interruptionMode === \"none\") {\n      micButton.style.cursor = \"default\";\n    }\n  };\n\n  const applyRuntypeMicSpeakingStyles = () => {\n    if (!micButton) return;\n    storeOriginalMicStyles();\n    const voiceConfig = config.voiceRecognition ?? {};\n    const interruptionMode = session.getVoiceInterruptionMode();\n    // Default icon depends on interruption mode:\n    // \"square\" for cancel, \"mic\" for barge-in (hot mic), \"volume-2\" otherwise\n    const defaultSpeakingIcon = interruptionMode === \"cancel\" ? \"square\"\n      : interruptionMode === \"barge-in\" ? \"mic\"\n      : \"volume-2\";\n    const iconName = voiceConfig.speakingIconName ?? defaultSpeakingIcon;\n    const iconColor = voiceConfig.speakingIconColor\n      ?? (interruptionMode === \"barge-in\" ? (voiceConfig.recordingIconColor ?? originalMicStyles?.color ?? \"\") : (originalMicStyles?.color ?? \"\"));\n    const bgColor = voiceConfig.speakingBackgroundColor\n      ?? (interruptionMode === \"barge-in\" ? (voiceConfig.recordingBackgroundColor ?? \"var(--persona-voice-recording-bg, #ef4444)\") : (originalMicStyles?.backgroundColor ?? \"\"));\n    const borderColor = voiceConfig.speakingBorderColor\n      ?? (interruptionMode === \"barge-in\" ? (voiceConfig.recordingBorderColor ?? \"\") : (originalMicStyles?.borderColor ?? \"\"));\n\n    removeAllVoiceStateClasses();\n    micButton.classList.add(\"persona-voice-speaking\");\n    micButton.style.backgroundColor = bgColor;\n    micButton.style.borderColor = borderColor;\n    const resolvedColor = iconColor || \"currentColor\";\n    micButton.style.color = resolvedColor;\n    swapMicIcon(iconName, resolvedColor);\n\n    // aria-label varies by interruption mode\n    const ariaLabel = interruptionMode === \"cancel\"\n      ? \"Stop playback and re-record\"\n      : interruptionMode === \"barge-in\"\n      ? \"Speak to interrupt\"\n      : \"Agent is speaking\";\n    micButton.setAttribute(\"aria-label\", ariaLabel);\n    // In \"none\" mode the button is not actionable during speaking\n    if (interruptionMode === \"none\") {\n      micButton.style.cursor = \"default\";\n    }\n    // In \"barge-in\" mode, add recording class to show mic is hot\n    if (interruptionMode === \"barge-in\") {\n      micButton.classList.add(\"persona-voice-recording\");\n    }\n  };\n\n  /** Restore mic button to idle state (icon, colors, aria-label, cursor) */\n  const removeRuntypeMicStateStyles = () => {\n    if (!micButton) return;\n    removeAllVoiceStateClasses();\n    if (originalMicStyles) {\n      micButton.style.backgroundColor = originalMicStyles.backgroundColor ?? \"\";\n      micButton.style.color = originalMicStyles.color ?? \"\";\n      micButton.style.borderColor = originalMicStyles.borderColor ?? \"\";\n      swapMicIcon(originalMicStyles.iconName, originalMicStyles.color || \"currentColor\");\n      originalMicStyles = null;\n    }\n    micButton.style.cursor = \"\";\n    micButton.setAttribute(\"aria-label\", \"Start voice recognition\");\n  };\n\n  // Wire up mic button click handler\n  const handleMicButtonClick = () => {\n    // Runtype provider: use session.toggleVoice() (WebSocket-based STT)\n    if (config.voiceRecognition?.provider?.type === 'runtype') {\n      const voiceStatus = session.getVoiceStatus();\n      const interruptionMode = session.getVoiceInterruptionMode();\n\n      // In \"none\" mode, ignore clicks while processing or speaking\n      if (interruptionMode === \"none\" &&\n          (voiceStatus === \"processing\" || voiceStatus === \"speaking\")) {\n        return;\n      }\n\n      // In \"cancel\" mode during processing/speaking: stop playback only\n      if (interruptionMode === \"cancel\" &&\n          (voiceStatus === \"processing\" || voiceStatus === \"speaking\")) {\n        session.stopVoicePlayback();\n        return;\n      }\n\n      // In barge-in mode, clicking mic = \"hang up\" (any state: speaking, idle, etc.)\n      // Stops playback if active, tears down the always-on mic.\n      if (session.isBargeInActive()) {\n        session.stopVoicePlayback();\n        session.deactivateBargeIn().then(() => {\n          voiceState.active = false;\n          voiceState.manuallyDeactivated = true;\n          persistVoiceMetadata();\n          emitVoiceState(\"user\");\n          removeRuntypeMicStateStyles();\n        });\n        return;\n      }\n\n      session.toggleVoice().then(() => {\n        voiceState.active = session.isVoiceActive();\n        voiceState.manuallyDeactivated = !session.isVoiceActive();\n        persistVoiceMetadata();\n        emitVoiceState(\"user\");\n        if (session.isVoiceActive()) {\n          applyRuntypeMicRecordingStyles();\n        } else {\n          removeRuntypeMicStateStyles();\n        }\n      });\n      return;\n    }\n\n    // Browser provider: use SpeechRecognition\n    if (isRecording) {\n      // Stop recording and submit\n      const finalValue = textarea.value.trim();\n      voiceState.manuallyDeactivated = true;\n      persistVoiceMetadata();\n      stopVoiceRecognition(\"user\");\n      if (finalValue) {\n        textarea.value = \"\";\n        textarea.style.height = \"auto\"; // Reset height after clearing\n        session.sendMessage(finalValue);\n      }\n    } else {\n      // Start recording\n      voiceState.manuallyDeactivated = false;\n      persistVoiceMetadata();\n      startVoiceRecognition(\"user\");\n    }\n  };\n\n  composerVoiceBridge = handleMicButtonClick;\n\n  if (micButton) {\n    micButton.addEventListener(\"click\", handleMicButtonClick);\n\n    destroyCallbacks.push(() => {\n      if (config.voiceRecognition?.provider?.type === 'runtype') {\n        if (session.isVoiceActive()) session.toggleVoice();\n        removeRuntypeMicStateStyles();\n      } else {\n        stopVoiceRecognition(\"system\");\n      }\n      if (micButton) {\n        micButton.removeEventListener(\"click\", handleMicButtonClick);\n      }\n    });\n  }\n\n  const autoResumeUnsub = eventBus.on(\"assistant:complete\", () => {\n    if (!voiceAutoResumeMode) return;\n    if (voiceState.active || voiceState.manuallyDeactivated) return;\n    if (voiceAutoResumeMode === \"assistant\" && !voiceState.lastUserMessageWasVoice) {\n      return;\n    }\n    setTimeout(() => {\n      if (!voiceState.active && !voiceState.manuallyDeactivated) {\n        if (config.voiceRecognition?.provider?.type === 'runtype') {\n          session.toggleVoice().then(() => {\n            voiceState.active = session.isVoiceActive();\n            emitVoiceState(\"auto\");\n            if (session.isVoiceActive()) applyRuntypeMicRecordingStyles();\n          });\n        } else {\n          startVoiceRecognition(\"auto\");\n        }\n      }\n    }, 600);\n  });\n  destroyCallbacks.push(autoResumeUnsub);\n\n  // Handle action:resubmit event - automatically trigger another model call\n  // when an action handler needs the model to continue processing (e.g., analyzing search results)\n  const resubmitUnsub = eventBus.on(\"action:resubmit\", () => {\n    // Short delay to allow UI to update with any injected messages\n    // Handlers should call context.triggerResubmit() AFTER their async work completes\n    setTimeout(() => {\n      if (session && !session.isStreaming()) {\n        // Continue conversation without adding a visible user message\n        session.continueConversation();\n      }\n    }, 100);\n  });\n  destroyCallbacks.push(resubmitUnsub);\n\n  const toggleOpen = () => {\n    setOpenState(!open, \"user\");\n  };\n\n  // Plugin hook: renderLauncher - allow plugins to provide custom launcher\n  let launcherButtonInstance: LauncherButton | null = null;\n  let customLauncherElement: HTMLElement | null = null;\n  \n  // Composer-bar mode is launcher-less by design: the persistent pill IS the\n  // entry point, so skip creating any launcher button (default or plugin).\n  if (launcherEnabled && !isComposerBar()) {\n    const { instance, element } = resolveLauncher({ config, plugins, onToggle: toggleOpen });\n    launcherButtonInstance = instance;\n    // A plugin-provided launcher returns no controller instance; track its\n    // element separately so the update path can manage it.\n    if (!instance) customLauncherElement = element;\n  }\n\n  if (launcherButtonInstance) {\n    mount.appendChild(launcherButtonInstance.element);\n  } else if (customLauncherElement) {\n    mount.appendChild(customLauncherElement);\n  }\n  updateOpenState();\n  renderSuggestions();\n  updateCopy();\n  setComposerDisabled(session.isStreaming());\n  // Reopen-where-left-off takes precedence when opted in (Principle 11);\n  // otherwise fall back to the historical per-mode positioning.\n  if (!restoreScrollPosition()) {\n    if (getScrollMode() === \"follow\") {\n      scheduleAutoScroll(true);\n    } else {\n      jumpToBottomInstant();\n    }\n  }\n  maybeRestoreVoiceFromMetadata();\n\n  if (autoFocusInput) {\n    // Composer-bar's pill exposes the textarea immediately, so focus it on\n    // init like the inline embed does: even though the panel is collapsed.\n    if (!launcherEnabled || isComposerBar()) {\n      setTimeout(() => maybeFocusInput(), 0);\n    } else if (open) {\n      setTimeout(() => maybeFocusInput(), 200);\n    }\n  }\n\n  const recalcPanelHeight = () => {\n    // Composer-bar mode lets CSS own all sizing: collapsed pill is auto-sized\n    // by the footer; expanded fullscreen/modal are driven by CSS attribute\n    // selectors plus inline maxWidth/maxHeight set in updateOpenState. JS\n    // sizing here would fight the morph transitions.\n    if (isComposerBar()) {\n      updateScrollToBottomButtonOffset();\n      updateOpenState();\n      return;\n    }\n\n    const dockedMode = isDockedMountMode(config);\n    const sidebarMode = config.launcher?.sidebarMode ?? false;\n    const fullHeight = dockedMode || sidebarMode || (config.launcher?.fullHeight ?? false);\n\n    // Mobile fullscreen: re-apply fullscreen styles on resize (handles orientation changes)\n    const ownerWindow = mount.ownerDocument.defaultView ?? window;\n    const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;\n    const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;\n    const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;\n    const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;\n\n    try {\n      if (shouldGoFullscreen) {\n        // syncPanelChrome re-applies theme + artifact layout vars that the\n        // cssText reset in applyFullHeightStyles wipes; bare re-style flickers.\n        syncPanelChrome();\n        appliedSplitMode = splitChromeMode();\n        return;\n      }\n\n      // Exiting mobile fullscreen (e.g., orientation change to landscape): reset all styles\n      let chromeResynced = false;\n      if (wasMobileFullscreen) {\n        wasMobileFullscreen = false;\n        syncPanelChrome();\n        chromeResynced = true;\n      }\n\n      // Width-only resize can cross the 640 split boundary without changing\n      // panel geometry (pane ResizeObserver stays silent) or hitting the\n      // fullscreen branches, so resync chrome when the split mode flips.\n      const modeNow = splitChromeMode();\n      if (!chromeResynced && modeNow !== appliedSplitMode) {\n        syncPanelChrome();\n        chromeResynced = true;\n      }\n      appliedSplitMode = modeNow;\n      // syncPanelChrome does not touch the resize handle, so a width-only flip\n      // between welded (seam overlay) and detached/flex handle would leave it in\n      // the old mode. reconcileArtifactResize re-places it; it never re-enters\n      // syncArtifactPane, so no recursion. No-op when artifacts are disabled.\n      if (chromeResynced) reconcileArtifactResize();\n\n      if (!launcherEnabled && !dockedMode) {\n        panel.style.height = \"\";\n        panel.style.width = \"\";\n        return;\n      }\n\n      // In sidebar/fullHeight mode, don't override the width - it's handled by applyFullHeightStyles\n      if (!sidebarMode && !dockedMode) {\n        const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;\n        const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;\n        panel.style.width = width;\n        panel.style.maxWidth = width;\n      }\n      applyLauncherArtifactPanelWidth();\n\n      // In fullHeight mode, don't set a fixed height\n      if (!fullHeight) {\n        const viewportHeight = ownerWindow.innerHeight;\n        const verticalMargin = 64; // leave space for launcher's offset\n        const heightOffset = config.launcher?.heightOffset ?? 0;\n        const available = Math.max(200, viewportHeight - verticalMargin);\n        const clamped = Math.min(640, available);\n        const finalHeight = Math.max(200, clamped - heightOffset);\n        panel.style.height = `${finalHeight}px`;\n      }\n    } finally {\n      // applyFullHeightStyles() assigns wrapper.style.cssText (e.g. display:flex !important), which\n      // overwrites updateOpenState()'s display:none when docked+closed. Re-sync after every recalc.\n      updateScrollToBottomButtonOffset();\n      updateOpenState();\n\n      // Sync scroll lock and host stacking when viewport mode changes (e.g. orientation change)\n      if (open && launcherEnabled) {\n        const ow = mount.ownerDocument.defaultView ?? window;\n        const isMobile = ow.innerWidth <= (config.launcher?.mobileBreakpoint ?? 640);\n        const sm = config.launcher?.sidebarMode ?? false;\n        const mf = config.launcher?.mobileFullscreen ?? true;\n        const dockedMF = isDockedMountMode(config) && mf && isMobile;\n        const isVC = sm || (mf && isMobile && launcherEnabled) || dockedMF;\n\n        if (isVC && !releaseScrollLock) {\n          const root = mount.getRootNode();\n          const hostEl = root instanceof ShadowRoot\n            ? (root.host as HTMLElement)\n            : mount.closest<HTMLElement>(\".persona-host\");\n          if (hostEl && !teardownHostStacking) {\n            teardownHostStacking = syncOverlayHostStacking(\n              hostEl,\n              config.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX\n            );\n          }\n          releaseScrollLock = acquireScrollLock(mount.ownerDocument);\n        } else if (!isVC) {\n          teardownHostStacking?.();\n          teardownHostStacking = null;\n          releaseScrollLock?.();\n          releaseScrollLock = null;\n        }\n      }\n    }\n  };\n\n  recalcPanelHeight();\n  const ownerWindow = mount.ownerDocument.defaultView ?? window;\n  ownerWindow.addEventListener(\"resize\", recalcPanelHeight);\n  destroyCallbacks.push(() => ownerWindow.removeEventListener(\"resize\", recalcPanelHeight));\n  if (typeof ResizeObserver !== \"undefined\") {\n    const footerResizeObserver = new ResizeObserver(() => {\n      updateScrollToBottomButtonOffset();\n    });\n    footerResizeObserver.observe(footer);\n    destroyCallbacks.push(() => footerResizeObserver.disconnect());\n  }\n\n  lastScrollTop = body.scrollTop;\n  let lastBottomOffset = getScrollBottomOffset(body);\n\n  const getTranscriptSelection = (): Selection | null => {\n    // Selections inside a shadow root are not always reflected by\n    // document.getSelection(); prefer the shadow root's view when available\n    // (non-standard but supported where it matters).\n    const root = body.getRootNode();\n    const shadowSelection =\n      typeof (root as ShadowRoot & { getSelection?: () => Selection | null })\n        .getSelection === \"function\"\n        ? (root as ShadowRoot & { getSelection: () => Selection | null }).getSelection()\n        : null;\n    return shadowSelection ?? body.ownerDocument.getSelection();\n  };\n  const hasActiveTranscriptSelection = () =>\n    hasSelectionWithin(getTranscriptSelection(), body);\n\n  const handleScroll = () => {\n    const scrollTop = body.scrollTop;\n    // When content mutates (e.g. stream-animation plugins re-rendering text)\n    // or the viewport grows (composer shrinking back), the maximum scroll\n    // position can shrink and force the browser to clamp scrollTop downward.\n    // That emits a scroll event with a negative delta that would otherwise be\n    // misread as the user scrolling up, pausing auto-follow and flashing the\n    // scroll-to-bottom button. Treat those as non-user events. Tracking the\n    // bottom offset (scrollHeight - clientHeight) rather than scrollHeight\n    // alone also covers clientHeight-driven clamps.\n    const currentBottomOffset = getScrollBottomOffset(body);\n    const bottomOffsetShrank = currentBottomOffset < lastBottomOffset;\n    lastBottomOffset = currentBottomOffset;\n\n    if (!isFollowEffective()) {\n      // No follow state to manage (anchored anchor-top / none): just keep the\n      // scroll-to-bottom affordance in sync with the user's position.\n      lastScrollTop = scrollTop;\n      syncScrollToBottomButton();\n      return;\n    }\n\n    const { action, nextLastScrollTop } = resolveFollowStateFromScroll({\n      following: autoFollow.isFollowing(),\n      currentScrollTop: scrollTop,\n      lastScrollTop,\n      nearBottom: isElementNearBottom(body, BOTTOM_THRESHOLD),\n      userScrollThreshold: USER_SCROLL_THRESHOLD,\n      isAutoScrolling: isAutoScrolling || hasPendingAutoScroll || bottomOffsetShrank,\n      pauseOnUpwardScroll: true,\n      pauseWhenAwayFromBottom: false,\n      resumeRequiresDownwardScroll: true\n    });\n    lastScrollTop = nextLastScrollTop;\n\n    if (action === \"resume\") {\n      // Drag-selecting downward near the bottom edge auto-scrolls down and\n      // would otherwise read as a resume gesture; keep follow paused while a\n      // transcript selection is active so it isn't yanked mid-drag.\n      if (!hasActiveTranscriptSelection()) {\n        resumeAutoScroll();\n      }\n      return;\n    }\n\n    if (action === \"pause\") {\n      pauseAutoScroll();\n    }\n  };\n\n  body.addEventListener(\"scroll\", handleScroll, { passive: true });\n  destroyCallbacks.push(() => body.removeEventListener(\"scroll\", handleScroll));\n\n  // Content-growth follow. Render events already schedule auto-scroll, but\n  // content can also grow without one: images/embeds finishing loading\n  // mid-stream, web fonts swapping, the panel or composer resizing. Observe\n  // the messages wrapper (content growth) and the scroll container itself\n  // (viewport resize) so the pin survives all of them.\n  if (typeof ResizeObserver !== \"undefined\") {\n    const contentResizeObserver = new ResizeObserver(() => {\n      handleContentResize();\n    });\n    contentResizeObserver.observe(messagesWrapper);\n    contentResizeObserver.observe(body);\n    destroyCallbacks.push(() => contentResizeObserver.disconnect());\n  }\n\n  // Pause auto-follow while the user selects transcript text so the\n  // streaming scroll doesn't move content out from under the selection.\n  // Driven purely by selectionchange (no pointer gating) so keyboard\n  // selection (Shift+arrows, select-all) pauses too; a stale selection\n  // left in the transcript fires no further events, so it can't re-pause\n  // after the user resumes following.\n  const handleSelectionChange = () => {\n    if (!isFollowEffective()) return;\n    if (!autoFollow.isFollowing()) return;\n    if (hasActiveTranscriptSelection()) {\n      pauseAutoScroll();\n    }\n  };\n  const selectionDocument = body.ownerDocument;\n  selectionDocument.addEventListener(\"selectionchange\", handleSelectionChange);\n  destroyCallbacks.push(() => {\n    selectionDocument.removeEventListener(\"selectionchange\", handleSelectionChange);\n  });\n\n  // Principle 3: every interaction is intent. Beyond wheel/scroll/selection,\n  // opting into `pauseOnInteraction` also treats keyboard navigation within the\n  // transcript and focusing an interactive element (a link, button, etc.) as\n  // \"the reader is doing something here\" — pause auto-follow so the stream\n  // doesn't move content out from under them. Opt-in; off by default.\n  const NAV_KEYS = new Set([\n    \"PageUp\",\n    \"PageDown\",\n    \"Home\",\n    \"End\",\n    \"ArrowUp\",\n    \"ArrowDown\",\n  ]);\n  const handleTranscriptKeydown = (event: KeyboardEvent) => {\n    if (!isPauseOnInteractionEnabled()) return;\n    if (!isFollowEffective()) return;\n    if (!autoFollow.isFollowing()) return;\n    if (NAV_KEYS.has(event.key)) {\n      pauseAutoScroll();\n    }\n  };\n  const handleTranscriptFocusIn = (event: FocusEvent) => {\n    if (!isPauseOnInteractionEnabled()) return;\n    if (!isFollowEffective()) return;\n    if (!autoFollow.isFollowing()) return;\n    const target = event.target as Element | null;\n    if (target && target.closest(\"a, button, [tabindex], input, textarea, select\")) {\n      pauseAutoScroll();\n    }\n  };\n  body.addEventListener(\"keydown\", handleTranscriptKeydown);\n  body.addEventListener(\"focusin\", handleTranscriptFocusIn);\n  destroyCallbacks.push(() => {\n    body.removeEventListener(\"keydown\", handleTranscriptKeydown);\n    body.removeEventListener(\"focusin\", handleTranscriptFocusIn);\n  });\n\n  const handleWheel = (event: WheelEvent) => {\n    if (!isFollowEffective()) return;\n    const action = resolveFollowStateFromWheel({\n      following: autoFollow.isFollowing(),\n      deltaY: event.deltaY,\n      nearBottom: isElementNearBottom(body, BOTTOM_THRESHOLD),\n      resumeWhenNearBottom: true\n    });\n\n    if (action === \"pause\") {\n      pauseAutoScroll();\n    } else if (action === \"resume\" && !hasActiveTranscriptSelection()) {\n      resumeAutoScroll();\n    }\n  };\n  body.addEventListener(\"wheel\", handleWheel, { passive: true });\n  destroyCallbacks.push(() => body.removeEventListener(\"wheel\", handleWheel));\n  scrollToBottomButton.addEventListener(\"click\", () => {\n    // Jumping to the latest abandons the current anchor: drop the spacer\n    // first so \"bottom\" means the real end of content, not spacer padding\n    // that would keep shrinking underneath the reader.\n    resetAnchorState();\n    body.scrollTop = body.scrollHeight;\n    lastScrollTop = body.scrollTop;\n    resumeAutoScroll();\n    scheduleAutoScroll(true);\n    syncScrollToBottomButton();\n  });\n  destroyCallbacks.push(() => scrollToBottomButton.remove());\n  destroyCallbacks.push(() => {\n    cancelAutoScroll();\n    resetAnchorState();\n  });\n\n  const refreshCloseButton = () => {\n    if (!closeButton) return;\n    if (closeHandler) {\n      closeButton.removeEventListener(\"click\", closeHandler);\n      closeHandler = null;\n    }\n    if (isPanelToggleable()) {\n      closeButton.style.display = \"\";\n      closeHandler = () => {\n        setOpenState(false, \"user\");\n      };\n      closeButton.addEventListener(\"click\", closeHandler);\n    } else {\n      closeButton.style.display = \"none\";\n    }\n  };\n\n  refreshCloseButton();\n\n  // Setup clear chat button click handler\n  const setupClearChatButton = () => {\n    const { clearChatButton } = panelElements;\n    if (!clearChatButton) return;\n\n    clearChatButton.addEventListener(\"click\", () => {\n      // Clear messages in session (this will trigger onMessagesChanged which re-renders)\n      session.clearMessages();\n      messageCache.clear();\n      resumeAutoScroll();\n\n      // Drop any open ask_user_question sheets: their source messages are gone.\n      removeAskUserQuestionSheet(panelElements.composerOverlay);\n\n      // Always clear the default localStorage key\n      try {\n        localStorage.removeItem(DEFAULT_CHAT_HISTORY_STORAGE_KEY);\n        if (config.debug) {\n          console.log(`[AgentWidget] Cleared default localStorage key: ${DEFAULT_CHAT_HISTORY_STORAGE_KEY}`);\n        }\n      } catch (error) {\n        console.error(\"[AgentWidget] Failed to clear default localStorage:\", error);\n      }\n\n      // Also clear custom localStorage key if configured\n      if (config.clearChatHistoryStorageKey && config.clearChatHistoryStorageKey !== DEFAULT_CHAT_HISTORY_STORAGE_KEY) {\n        try {\n          localStorage.removeItem(config.clearChatHistoryStorageKey);\n          if (config.debug) {\n            console.log(`[AgentWidget] Cleared custom localStorage key: ${config.clearChatHistoryStorageKey}`);\n          }\n        } catch (error) {\n          console.error(\"[AgentWidget] Failed to clear custom localStorage:\", error);\n        }\n      }\n\n      // Dispatch custom event for external handlers (e.g., localStorage clearing in examples)\n      const clearEvent = new CustomEvent(\"persona:clear-chat\", {\n        detail: { timestamp: new Date().toISOString() }\n      });\n      window.dispatchEvent(clearEvent);\n\n      if (storageAdapter?.clear) {\n        runStorageMutation(\n          () => storageAdapter.clear!(),\n          \"[AgentWidget] Failed to clear storage adapter:\"\n        );\n      }\n      persistentMetadata = {};\n      actionManager.syncFromMetadata();\n\n      // Clear event stream buffer and store, and reset throughput tracking\n      eventStreamBuffer?.clear();\n      throughputTracker?.reset();\n      eventStreamView?.update();\n    });\n  };\n\n  setupClearChatButton();\n\n  if (composerForm) {\n    composerForm.addEventListener(\"submit\", handleSubmit);\n  }\n  textarea?.addEventListener(\"keydown\", handleComposerKeydown);\n  textarea?.addEventListener(\"input\", handleComposerInput);\n  textarea?.addEventListener(\"paste\", handleInputPaste);\n\n  const escStopDoc = mount.ownerDocument ?? document;\n  escStopDoc.addEventListener(\"keydown\", handleEscStop, true);\n\n  const ATTACHMENT_DROP_ACTIVE_CLASS = \"persona-attachment-drop-active\";\n  let attachmentFileDragDepth = 0;\n\n  const clearAttachmentDropVisual = () => {\n    attachmentFileDragDepth = 0;\n    container.classList.remove(ATTACHMENT_DROP_ACTIVE_CLASS);\n  };\n\n  const attachmentDropHandlingActive = (): boolean =>\n    config.attachments?.enabled === true && attachmentManager !== null;\n\n  // Visual highlight tracked on `container` (the chat column).\n  const handleAttachmentDragEnterCapture = (e: DragEvent) => {\n    if (!dataTransferHasFiles(e.dataTransfer) || !attachmentDropHandlingActive()) return;\n    attachmentFileDragDepth++;\n    if (attachmentFileDragDepth === 1) {\n      container.classList.add(ATTACHMENT_DROP_ACTIVE_CLASS);\n    }\n  };\n\n  const handleAttachmentDragLeaveCapture = (e: DragEvent) => {\n    if (!dataTransferHasFiles(e.dataTransfer) || !attachmentDropHandlingActive()) return;\n    attachmentFileDragDepth--;\n    if (attachmentFileDragDepth <= 0) {\n      clearAttachmentDropVisual();\n    }\n  };\n\n  // dragover + drop registered on `mount` so the browser default (open file)\n  // is suppressed across the entire widget surface (artifact pane, gaps, etc.).\n  const handleAttachmentDragOverCapture = (e: DragEvent) => {\n    if (!dataTransferHasFiles(e.dataTransfer) || !attachmentDropHandlingActive()) return;\n    e.preventDefault();\n    e.dataTransfer.dropEffect = \"copy\";\n  };\n\n  const handleAttachmentDropCapture = (e: DragEvent) => {\n    if (!dataTransferHasFiles(e.dataTransfer) || !attachmentDropHandlingActive()) return;\n    e.preventDefault();\n    e.stopPropagation();\n    clearAttachmentDropVisual();\n    const files = Array.from(e.dataTransfer.files ?? []);\n    if (files.length === 0) return;\n    void attachmentManager!.handleFiles(files);\n  };\n\n  const attachmentDropCapture = true;\n  container.addEventListener(\"dragenter\", handleAttachmentDragEnterCapture, attachmentDropCapture);\n  container.addEventListener(\"dragleave\", handleAttachmentDragLeaveCapture, attachmentDropCapture);\n  mount.addEventListener(\"dragover\", handleAttachmentDragOverCapture, attachmentDropCapture);\n  mount.addEventListener(\"drop\", handleAttachmentDropCapture, attachmentDropCapture);\n\n  // Prevent the browser from navigating to/opening a dropped file anywhere on\n  // the page while this widget instance has attachments enabled.  These guards\n  // intentionally skip the `dataTransferHasFiles` check because real OS drags\n  // may expose `dataTransfer.types` as a DOMStringList or restrict access\n  // during certain drag phases.  The cost is minimal: we suppress the native\n  // \"open file\" default for ALL drag-overs while the widget is alive and\n  // attachments are on: text drags into the textarea still work because\n  // element-level handlers are unaffected (we don't stopPropagation here).\n  const ownerDoc = mount.ownerDocument;\n  const handleDocDragOver = (e: DragEvent) => {\n    if (!attachmentDropHandlingActive()) return;\n    e.preventDefault();\n  };\n  const handleDocDrop = (e: DragEvent) => {\n    if (!attachmentDropHandlingActive()) return;\n    e.preventDefault();\n  };\n  ownerDoc.addEventListener(\"dragover\", handleDocDragOver);\n  ownerDoc.addEventListener(\"drop\", handleDocDrop);\n\n  destroyCallbacks.push(() => {\n    if (composerForm) {\n      composerForm.removeEventListener(\"submit\", handleSubmit);\n    }\n    textarea?.removeEventListener(\"keydown\", handleComposerKeydown);\n    textarea?.removeEventListener(\"input\", handleComposerInput);\n    textarea?.removeEventListener(\"paste\", handleInputPaste);\n    escStopDoc.removeEventListener(\"keydown\", handleEscStop, true);\n  });\n\n  destroyCallbacks.push(() => {\n    container.removeEventListener(\"dragenter\", handleAttachmentDragEnterCapture, attachmentDropCapture);\n    container.removeEventListener(\"dragleave\", handleAttachmentDragLeaveCapture, attachmentDropCapture);\n    mount.removeEventListener(\"dragover\", handleAttachmentDragOverCapture, attachmentDropCapture);\n    mount.removeEventListener(\"drop\", handleAttachmentDropCapture, attachmentDropCapture);\n    ownerDoc.removeEventListener(\"dragover\", handleDocDragOver);\n    ownerDoc.removeEventListener(\"drop\", handleDocDrop);\n    clearAttachmentDropVisual();\n  });\n\n  destroyCallbacks.push(() => {\n    session.cancel();\n  });\n\n  if (launcherButtonInstance) {\n    destroyCallbacks.push(() => {\n      launcherButtonInstance?.destroy();\n    });\n  } else if (customLauncherElement) {\n    destroyCallbacks.push(() => {\n      customLauncherElement?.remove();\n    });\n  }\n\n  const controller: Controller = {\n    update(nextConfig: AgentWidgetConfig) {\n      const previousToolCallConfig = config.toolCall;\n      const previousMessageActions = config.messageActions;\n      const previousLayoutMessages = config.layout?.messages;\n      const previousColorScheme = config.colorScheme;\n      const previousLoadingIndicator = config.loadingIndicator;\n      const previousIterationDisplay = config.iterationDisplay;\n      const previousShowReasoning = config.features?.showReasoning;\n      const previousShowToolCalls = config.features?.showToolCalls;\n      const previousToolCallDisplay = config.features?.toolCallDisplay;\n      const previousReasoningDisplay = config.features?.reasoningDisplay;\n      const previousStreamAnimationType = config.features?.streamAnimation?.type;\n      config = { ...config, ...nextConfig };\n      // applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables\n      applyFullHeightStyles();\n      applyThemeVariables(mount, config);\n      applyArtifactLayoutCssVars(mount, config);\n      applyArtifactPaneAppearance(mount, config);\n      syncArtifactPane();\n\n      // Re-setup theme observer if colorScheme changed\n      if (config.colorScheme !== previousColorScheme) {\n        setupThemeObserver();\n      }\n\n      // Update plugins\n      const newPlugins = pluginRegistry.getForInstance(config.plugins);\n      plugins.length = 0;\n      plugins.push(...newPlugins);\n\n      launcherEnabled = config.launcher?.enabled ?? true;\n      autoExpand = config.launcher?.autoExpand ?? false;\n      showReasoning = config.features?.showReasoning ?? true;\n      showToolCalls = config.features?.showToolCalls ?? true;\n      scrollToBottomFeature = config.features?.scrollToBottom ?? {};\n      const prevScrollMode = getScrollMode();\n      scrollBehaviorFeature = config.features?.scrollBehavior ?? {};\n      if (prevScrollMode !== getScrollMode()) {\n        // Leaving anchor-top drops any live spacer; entering a new mode\n        // starts from a clean follow state.\n        resetAnchorState();\n        resumeAutoScroll();\n      }\n      renderScrollToBottomButton();\n      syncScrollToBottomButton();\n      const prevShowEventStreamToggle = showEventStreamToggle;\n      showEventStreamToggle = config.features?.showEventStreamToggle ?? false;\n\n      // Handle dynamic event stream feature flag toggling\n      if (showEventStreamToggle && !prevShowEventStreamToggle) {\n        // Flag changed from false to true - create buffer/store if needed\n        if (!eventStreamBuffer) {\n          eventStreamStore = new EventStreamStore(eventStreamDbName);\n          eventStreamBuffer = new EventStreamBuffer(eventStreamMaxEvents, eventStreamStore);\n          throughputTracker = throughputTracker ?? new ThroughputTracker();\n          eventStreamStore.open().then(() => eventStreamBuffer?.restore()).catch(() => {});\n          // Register the SSE event callback (host tap + buffer + throughput)\n          session.setSSEEventCallback((type: string, payload: unknown) => {\n            config.onSSEEvent?.(type, payload);\n            throughputTracker?.processEvent(type, payload);\n            eventStreamBuffer!.push({\n              id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n              type,\n              timestamp: Date.now(),\n              payload: JSON.stringify(payload)\n            });\n          });\n        }\n        // Add header toggle button if not present\n        if (!eventStreamToggleBtn && header) {\n          const dynEsClassNames = config.features?.eventStream?.classNames;\n          const dynToggleBtnClasses = \"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-opacity-80 persona-cursor-pointer persona-border-none persona-bg-transparent persona-p-1\" + (dynEsClassNames?.toggleButton ? \" \" + dynEsClassNames.toggleButton : \"\");\n          eventStreamToggleBtn = createElement(\"button\", dynToggleBtnClasses) as HTMLButtonElement;\n          eventStreamToggleBtn.style.width = \"28px\";\n          eventStreamToggleBtn.style.height = \"28px\";\n          eventStreamToggleBtn.style.color = HEADER_THEME_CSS.actionIconColor;\n          eventStreamToggleBtn.type = \"button\";\n          eventStreamToggleBtn.setAttribute(\"aria-label\", \"Event Stream\");\n          eventStreamToggleBtn.title = \"Event Stream\";\n          const activityIcon = renderLucideIcon(\"activity\", \"18px\", \"currentColor\", 1.5);\n          if (activityIcon) eventStreamToggleBtn.appendChild(activityIcon);\n          const clearChatWrapper = panelElements.clearChatButtonWrapper;\n          const closeWrapper = panelElements.closeButtonWrapper;\n          const insertBefore = clearChatWrapper || closeWrapper;\n          if (insertBefore && insertBefore.parentNode === header) {\n            header.insertBefore(eventStreamToggleBtn, insertBefore);\n          } else {\n            header.appendChild(eventStreamToggleBtn);\n          }\n          eventStreamToggleBtn.addEventListener(\"click\", () => {\n            if (eventStreamVisible) {\n              toggleEventStreamOff();\n            } else {\n              toggleEventStreamOn();\n            }\n          });\n        }\n      } else if (!showEventStreamToggle && prevShowEventStreamToggle) {\n        // Flag changed from true to false - hide and clean up\n        toggleEventStreamOff();\n        if (eventStreamToggleBtn) {\n          eventStreamToggleBtn.remove();\n          eventStreamToggleBtn = null;\n        }\n        eventStreamBuffer?.clear();\n        eventStreamStore?.destroy();\n        eventStreamBuffer = null;\n        eventStreamStore = null;\n        throughputTracker?.reset();\n        throughputTracker = null;\n      }\n\n      if (config.launcher?.enabled === false && launcherButtonInstance) {\n        launcherButtonInstance.destroy();\n        launcherButtonInstance = null;\n      }\n      if (config.launcher?.enabled === false && customLauncherElement) {\n        customLauncherElement.remove();\n        customLauncherElement = null;\n      }\n\n      if (config.launcher?.enabled !== false && !launcherButtonInstance && !customLauncherElement) {\n        // Resolve the launcher again when re-enabling (honors renderLauncher plugin).\n        const { instance, element } = resolveLauncher({ config, plugins, onToggle: toggleOpen });\n        launcherButtonInstance = instance;\n        if (!instance) customLauncherElement = element;\n        mount.appendChild(element);\n      }\n\n      if (launcherButtonInstance) {\n        launcherButtonInstance.update(config);\n      }\n      // Note: Custom launcher updates are handled by the plugin's own logic\n\n      // Update panel header title and subtitle\n      if (headerTitle && config.launcher?.title !== undefined) {\n        headerTitle.textContent = config.launcher.title;\n      }\n      if (headerSubtitle && config.launcher?.subtitle !== undefined) {\n        headerSubtitle.textContent = config.launcher.subtitle;\n      }\n\n      // Update header layout if it changed\n      const headerLayoutConfig = config.layout?.header;\n      const headerLayoutChanged = headerLayoutConfig?.layout !== prevHeaderLayout;\n\n      if (headerLayoutChanged && header) {\n        // Rebuild header with new layout\n        const newHeaderElements = headerLayoutConfig\n          ? buildHeaderWithLayout(config, headerLayoutConfig, {\n              showClose: isPanelToggleable(),\n              onClose: () => setOpenState(false, \"user\")\n            })\n          : buildHeader({\n              config,\n              showClose: isPanelToggleable(),\n              onClose: () => setOpenState(false, \"user\")\n            });\n\n        // Replace the old header with the new one (keeps view.header in sync).\n        view.replaceHeader(newHeaderElements);\n\n        // Mirror the view's refreshed header refs into the local bindings.\n        header = view.header.element;\n        iconHolder = view.header.iconHolder;\n        headerTitle = view.header.headerTitle;\n        headerSubtitle = view.header.headerSubtitle;\n        closeButton = view.header.closeButton;\n\n        prevHeaderLayout = headerLayoutConfig?.layout;\n      } else if (headerLayoutConfig) {\n        // Apply visibility settings without rebuilding\n        if (iconHolder) {\n          iconHolder.style.display = headerLayoutConfig.showIcon === false ? \"none\" : \"\";\n        }\n        if (headerTitle) {\n          headerTitle.style.display = headerLayoutConfig.showTitle === false ? \"none\" : \"\";\n        }\n        if (headerSubtitle) {\n          headerSubtitle.style.display = headerLayoutConfig.showSubtitle === false ? \"none\" : \"\";\n        }\n        if (closeButton) {\n          closeButton.style.display = headerLayoutConfig.showCloseButton === false ? \"none\" : \"\";\n        }\n        if (panelElements.clearChatButtonWrapper) {\n          // showClearChat explicitly controls visibility when set\n          const showClearChat = headerLayoutConfig.showClearChat;\n          if (showClearChat !== undefined) {\n            panelElements.clearChatButtonWrapper.style.display = showClearChat ? \"\" : \"none\";\n            // When clear chat is hidden, close button needs ml-auto to stay right-aligned\n            const { closeButtonWrapper } = panelElements;\n            if (closeButtonWrapper && !closeButtonWrapper.classList.contains(\"persona-absolute\")) {\n              if (showClearChat) {\n                closeButtonWrapper.classList.remove(\"persona-ml-auto\");\n              } else {\n                closeButtonWrapper.classList.add(\"persona-ml-auto\");\n              }\n            }\n          }\n        }\n      }\n\n      // Update header visibility based on layout.showHeader\n      const showHeader = config.layout?.showHeader !== false; // default to true\n      if (header) {\n        header.style.display = showHeader ? \"\" : \"none\";\n      }\n\n      // Update footer visibility based on layout.showFooter\n      const showFooter = config.layout?.showFooter !== false; // default to true\n      if (footer) {\n        footer.style.display = showFooter ? \"\" : \"none\";\n      }\n      updateScrollToBottomButtonOffset();\n      syncScrollToBottomButton();\n\n      // Only update open state if launcher enabled state changed or autoExpand value changed\n      const launcherEnabledChanged = launcherEnabled !== prevLauncherEnabled;\n      const autoExpandChanged = autoExpand !== prevAutoExpand;\n\n      if (launcherEnabledChanged) {\n        // Launcher was enabled/disabled - update state accordingly\n        if (!launcherEnabled) {\n          // When launcher is disabled, always keep panel open\n          open = true;\n          updateOpenState();\n        } else {\n          // Launcher was just enabled - respect autoExpand setting\n          setOpenState(autoExpand, \"auto\");\n        }\n      } else if (autoExpandChanged) {\n        // autoExpand value changed - update state to match\n        setOpenState(autoExpand, \"auto\");\n      }\n      // Otherwise, preserve current open state (user may have manually opened/closed)\n\n      // Update previous values for next comparison\n      prevAutoExpand = autoExpand;\n      prevLauncherEnabled = launcherEnabled;\n      recalcPanelHeight();\n      refreshCloseButton();\n\n      // Re-render messages if config affecting message rendering changed\n      const toolCallConfigChanged = JSON.stringify(nextConfig.toolCall) !== JSON.stringify(previousToolCallConfig);\n      const messageActionsChanged = JSON.stringify(config.messageActions) !== JSON.stringify(previousMessageActions);\n      const layoutMessagesChanged = JSON.stringify(config.layout?.messages) !== JSON.stringify(previousLayoutMessages);\n      const loadingIndicatorChanged = config.loadingIndicator?.render !== previousLoadingIndicator?.render\n        || config.loadingIndicator?.renderIdle !== previousLoadingIndicator?.renderIdle\n        || config.loadingIndicator?.showBubble !== previousLoadingIndicator?.showBubble;\n      const iterationDisplayChanged = config.iterationDisplay !== previousIterationDisplay;\n      const featuresChanged = (config.features?.showReasoning ?? true) !== (previousShowReasoning ?? true)\n        || (config.features?.showToolCalls ?? true) !== (previousShowToolCalls ?? true)\n        || JSON.stringify(config.features?.toolCallDisplay) !== JSON.stringify(previousToolCallDisplay)\n        || JSON.stringify(config.features?.reasoningDisplay) !== JSON.stringify(previousReasoningDisplay);\n      const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged\n        || loadingIndicatorChanged || iterationDisplayChanged || featuresChanged;\n      if (messagesConfigChanged && session) {\n        configVersion++;\n        renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);\n      }\n\n      // Re-activate the stream-animation plugin when the type changes via\n      // update(). Built-in animations (typewriter, word-fade, letter-rise,\n      // pop-bubble) carry their CSS in widget.css, so swapping the type is\n      // enough. Plugin animations (wipe, glyph-cycle, and custom plugins\n      // registered via registerStreamAnimationPlugin) inject their styles and\n      // run onAttach only through ensurePluginActive, which the initial mount\n      // calls but update() otherwise skips. Without this, switching to a plugin\n      // animation live sets the type but never injects the CSS, so it silently\n      // does nothing. ensurePluginActive is idempotent (styles inject once per\n      // root), so re-selecting a previously-activated plugin is a no-op.\n      const nextStreamAnimationType = config.features?.streamAnimation?.type;\n      if (\n        nextStreamAnimationType !== previousStreamAnimationType &&\n        nextStreamAnimationType &&\n        nextStreamAnimationType !== \"none\"\n      ) {\n        const streamAnimationPlugin = resolveStreamAnimationPlugin(\n          nextStreamAnimationType,\n          config.features?.streamAnimation?.plugins\n        );\n        if (streamAnimationPlugin) ensurePluginActive(streamAnimationPlugin, mount);\n      }\n\n      // Update panel icon sizes\n      const launcher = config.launcher ?? {};\n      const headerIconHidden = launcher.headerIconHidden ?? false;\n      const layoutShowIcon = config.layout?.header?.showIcon;\n      // Hide icon if either headerIconHidden is true OR layout.header.showIcon is false\n      const shouldHideIcon = headerIconHidden || layoutShowIcon === false;\n      const headerIconName = launcher.headerIconName;\n      const headerIconSize = launcher.headerIconSize ?? \"48px\";\n\n      if (iconHolder) {\n        const headerEl = container.querySelector(\".persona-border-b-persona-divider\");\n        const headerCopy = headerEl?.querySelector(\".persona-flex-col\");\n\n        // Handle hide/show\n        if (shouldHideIcon) {\n          // Hide iconHolder\n          iconHolder.style.display = \"none\";\n          // Ensure headerCopy is still in header\n          if (headerEl && headerCopy && !headerEl.contains(headerCopy)) {\n            headerEl.insertBefore(headerCopy, headerEl.firstChild);\n          }\n        } else {\n          // Show iconHolder\n          iconHolder.style.display = \"\";\n          iconHolder.style.height = headerIconSize;\n          iconHolder.style.width = headerIconSize;\n          \n          // Ensure iconHolder is before headerCopy in header\n          if (headerEl && headerCopy) {\n            if (!headerEl.contains(iconHolder)) {\n              headerEl.insertBefore(iconHolder, headerCopy);\n            } else if (iconHolder.nextSibling !== headerCopy) {\n              // Reorder if needed\n              iconHolder.remove();\n              headerEl.insertBefore(iconHolder, headerCopy);\n            }\n          }\n          \n          // Update icon content based on priority: Lucide icon > iconUrl > agentIconText\n          if (headerIconName) {\n            // Use Lucide icon. Stroke `currentColor` (not a hardcoded white) so the\n            // glyph inherits iconHolder's `color: var(--persona-header-icon-fg, …)`,\n            // matching the initial render in header-builder.ts. Without this, any\n            // controller.update() (e.g. a theme-editor change) re-rendered the icon\n            // as white and the configured header icon color \"wouldn't stick\".\n            const iconSize = parseFloat(headerIconSize) || 24;\n            const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, \"currentColor\", 1);\n            if (iconSvg) {\n              iconHolder.replaceChildren(iconSvg);\n            } else {\n              // Fallback to agentIconText if Lucide icon fails\n              iconHolder.textContent = launcher.agentIconText ?? \"💬\";\n            }\n          } else if (launcher.iconUrl) {\n            // Use image URL\n            const img = iconHolder.querySelector(\"img\");\n            if (img) {\n              img.src = launcher.iconUrl;\n              img.style.height = headerIconSize;\n              img.style.width = headerIconSize;\n            } else {\n              // Create new img if it doesn't exist\n              const newImg = document.createElement(\"img\");\n              newImg.src = launcher.iconUrl;\n              newImg.alt = \"\";\n              newImg.className = \"persona-rounded-xl persona-object-cover\";\n              newImg.style.height = headerIconSize;\n              newImg.style.width = headerIconSize;\n              iconHolder.replaceChildren(newImg);\n            }\n          } else {\n            // Use text/emoji - clear any SVG or img first\n            const existingSvg = iconHolder.querySelector(\"svg\");\n            const existingImg = iconHolder.querySelector(\"img\");\n            if (existingSvg || existingImg) {\n              iconHolder.replaceChildren();\n            }\n            iconHolder.textContent = launcher.agentIconText ?? \"💬\";\n          }\n          \n          // Update image size if present\n          const img = iconHolder.querySelector(\"img\");\n          if (img) {\n            img.style.height = headerIconSize;\n            img.style.width = headerIconSize;\n          }\n        }\n      }\n\n      // Handle title/subtitle visibility from layout config\n      const layoutShowTitle = config.layout?.header?.showTitle;\n      const layoutShowSubtitle = config.layout?.header?.showSubtitle;\n      if (headerTitle) {\n        headerTitle.style.display = layoutShowTitle === false ? \"none\" : \"\";\n      }\n      if (headerSubtitle) {\n        headerSubtitle.style.display = layoutShowSubtitle === false ? \"none\" : \"\";\n      }\n\n      if (closeButton) {\n        // Handle close button visibility from layout config\n        const layoutShowCloseButton = config.layout?.header?.showCloseButton;\n        if (layoutShowCloseButton === false) {\n          closeButton.style.display = \"none\";\n        } else {\n          closeButton.style.display = \"\";\n        }\n\n        const closeButtonSize = launcher.closeButtonSize ?? \"32px\";\n        const closeButtonPlacement = launcher.closeButtonPlacement ?? \"inline\";\n        closeButton.style.height = closeButtonSize;\n        closeButton.style.width = closeButtonSize;\n        \n        // Update placement if changed - move the wrapper (not just the button) to preserve tooltip\n        const { closeButtonWrapper } = panelElements;\n        const isTopRight = closeButtonPlacement === \"top-right\";\n        const currentlyTopRight = closeButtonWrapper?.classList.contains(\"persona-absolute\");\n        \n        if (closeButtonWrapper && isTopRight !== currentlyTopRight) {\n          // Placement changed - need to move wrapper and update classes\n          closeButtonWrapper.remove();\n          \n          // Update wrapper classes\n          if (isTopRight) {\n            closeButtonWrapper.className = \"persona-absolute persona-top-4 persona-right-4 persona-z-50\";\n            container.style.position = \"relative\";\n            container.appendChild(closeButtonWrapper);\n          } else {\n            // Check if clear chat is inline to determine if we need ml-auto\n            const clearChatPlacement = launcher.clearChat?.placement ?? \"inline\";\n            const clearChatEnabled = launcher.clearChat?.enabled ?? true;\n            closeButtonWrapper.className = (clearChatEnabled && clearChatPlacement === \"inline\") ? \"\" : \"persona-ml-auto\";\n            // Find header element\n            const header = container.querySelector(\".persona-border-b-persona-divider\");\n            if (header) {\n              header.appendChild(closeButtonWrapper);\n            }\n          }\n        }\n        \n        // Close icon: launcher color wins; else theme.components.header.actionIconForeground\n        closeButton.style.color =\n          launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;\n        \n        if (launcher.closeButtonBackgroundColor) {\n          closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;\n          closeButton.classList.remove(\"hover:persona-bg-gray-100\");\n        } else {\n          closeButton.style.backgroundColor = \"\";\n          closeButton.classList.add(\"hover:persona-bg-gray-100\");\n        }\n        \n        // Apply border if width and/or color are provided\n        if (launcher.closeButtonBorderWidth || launcher.closeButtonBorderColor) {\n          const borderWidth = launcher.closeButtonBorderWidth || \"0px\";\n          const borderColor = launcher.closeButtonBorderColor || \"transparent\";\n          closeButton.style.border = `${borderWidth} solid ${borderColor}`;\n          closeButton.classList.remove(\"persona-border-none\");\n        } else {\n          closeButton.style.border = \"\";\n          closeButton.classList.add(\"persona-border-none\");\n        }\n        \n        if (launcher.closeButtonBorderRadius) {\n          closeButton.style.borderRadius = launcher.closeButtonBorderRadius;\n          closeButton.classList.remove(\"persona-rounded-full\");\n        } else {\n          closeButton.style.borderRadius = \"\";\n          closeButton.classList.add(\"persona-rounded-full\");\n        }\n\n        // Update padding\n        if (launcher.closeButtonPaddingX) {\n          closeButton.style.paddingLeft = launcher.closeButtonPaddingX;\n          closeButton.style.paddingRight = launcher.closeButtonPaddingX;\n        } else {\n          closeButton.style.paddingLeft = \"\";\n          closeButton.style.paddingRight = \"\";\n        }\n        if (launcher.closeButtonPaddingY) {\n          closeButton.style.paddingTop = launcher.closeButtonPaddingY;\n          closeButton.style.paddingBottom = launcher.closeButtonPaddingY;\n        } else {\n          closeButton.style.paddingTop = \"\";\n          closeButton.style.paddingBottom = \"\";\n        }\n\n        // Update icon\n        const closeButtonIconName = launcher.closeButtonIconName ?? \"x\";\n        const closeButtonIconText = launcher.closeButtonIconText ?? \"×\";\n\n        // Clear existing content and render new icon.\n        // Larger intrinsic size compensates for the X glyph's sparse\n        // viewBox so the close button visually matches sibling icons.\n        closeButton.innerHTML = \"\";\n        const iconSvg = renderLucideIcon(closeButtonIconName, \"28px\", \"currentColor\", 1);\n        if (iconSvg) {\n          closeButton.appendChild(iconSvg);\n        } else {\n          closeButton.textContent = closeButtonIconText;\n        }\n\n        // Update tooltip\n        const closeButtonTooltipText = launcher.closeButtonTooltipText ?? \"Close chat\";\n        const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;\n\n        closeButton.setAttribute(\"aria-label\", closeButtonTooltipText);\n\n        if (closeButtonWrapper) {\n          // Clean up old tooltip event listeners if they exist\n          if ((closeButtonWrapper as any)._cleanupTooltip) {\n            (closeButtonWrapper as any)._cleanupTooltip();\n            delete (closeButtonWrapper as any)._cleanupTooltip;\n          }\n\n          // Set up new portaled tooltip with event listeners\n          if (closeButtonShowTooltip && closeButtonTooltipText) {\n            let portaledTooltip: HTMLElement | null = null;\n\n            const showTooltip = () => {\n              if (portaledTooltip || !closeButton) return; // Already showing or button doesn't exist\n\n              const tooltipDocument = closeButton.ownerDocument;\n              const tooltipContainer = tooltipDocument.body;\n              if (!tooltipContainer) return;\n\n              // Create tooltip element\n              portaledTooltip = createElementInDocument(\n                tooltipDocument,\n                \"div\",\n                \"persona-clear-chat-tooltip\"\n              );\n              portaledTooltip.textContent = closeButtonTooltipText;\n\n              // Add arrow\n              const arrow = createElementInDocument(tooltipDocument, \"div\");\n              arrow.className = \"persona-clear-chat-tooltip-arrow\";\n              portaledTooltip.appendChild(arrow);\n\n              // Get button position\n              const buttonRect = closeButton.getBoundingClientRect();\n\n              // Position tooltip above button\n              portaledTooltip.style.position = \"fixed\";\n              portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);\n              portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;\n              portaledTooltip.style.top = `${buttonRect.top - 8}px`;\n              portaledTooltip.style.transform = \"translate(-50%, -100%)\";\n\n              // Append to body\n              tooltipContainer.appendChild(portaledTooltip);\n            };\n\n            const hideTooltip = () => {\n              if (portaledTooltip && portaledTooltip.parentNode) {\n                portaledTooltip.parentNode.removeChild(portaledTooltip);\n                portaledTooltip = null;\n              }\n            };\n\n            // Add event listeners\n            closeButtonWrapper.addEventListener(\"mouseenter\", showTooltip);\n            closeButtonWrapper.addEventListener(\"mouseleave\", hideTooltip);\n            closeButton.addEventListener(\"focus\", showTooltip);\n            closeButton.addEventListener(\"blur\", hideTooltip);\n\n            // Store cleanup function on the wrapper for later use\n            (closeButtonWrapper as any)._cleanupTooltip = () => {\n              hideTooltip();\n              if (closeButtonWrapper) {\n                closeButtonWrapper.removeEventListener(\"mouseenter\", showTooltip);\n                closeButtonWrapper.removeEventListener(\"mouseleave\", hideTooltip);\n              }\n              if (closeButton) {\n                closeButton.removeEventListener(\"focus\", showTooltip);\n                closeButton.removeEventListener(\"blur\", hideTooltip);\n              }\n            };\n          }\n        }\n      }\n\n      // Update clear chat button styling from config\n      const { clearChatButton, clearChatButtonWrapper } = panelElements;\n      if (clearChatButton) {\n        const clearChatConfig = launcher.clearChat ?? {};\n        const clearChatEnabled = clearChatConfig.enabled ?? true;\n        const layoutShowClearChat = config.layout?.header?.showClearChat;\n        // layout.header.showClearChat takes precedence if explicitly set\n        // Otherwise fall back to launcher.clearChat.enabled\n        const shouldShowClearChat = layoutShowClearChat !== undefined\n          ? layoutShowClearChat\n          : clearChatEnabled;\n        const clearChatPlacement = clearChatConfig.placement ?? \"inline\";\n\n        // Show/hide button based on layout config (primary) or launcher config (fallback)\n        if (clearChatButtonWrapper) {\n          clearChatButtonWrapper.style.display = shouldShowClearChat ? \"\" : \"none\";\n\n          // When clear chat is hidden, close button needs ml-auto to stay right-aligned.\n          // Composer-bar mode positions the close button absolutely, so the\n          // ml-auto layout shim doesn't apply and is skipped below.\n          const { closeButtonWrapper } = panelElements;\n          if (\n            !isComposerBar() &&\n            closeButtonWrapper &&\n            !closeButtonWrapper.classList.contains(\"persona-absolute\")\n          ) {\n            if (shouldShowClearChat) {\n              closeButtonWrapper.classList.remove(\"persona-ml-auto\");\n            } else {\n              closeButtonWrapper.classList.add(\"persona-ml-auto\");\n            }\n          }\n\n          // Update placement if changed. Composer-bar mode owns the clear\n          // button's position via panel.ts (absolute, top-right next to ×)\n          // and must not get reshuffled into the floating launcher's\n          // header strip.\n          const isTopRight = clearChatPlacement === \"top-right\";\n          const currentlyTopRight = clearChatButtonWrapper.classList.contains(\"persona-absolute\");\n\n          if (!isComposerBar() && isTopRight !== currentlyTopRight && shouldShowClearChat) {\n            clearChatButtonWrapper.remove();\n\n            if (isTopRight) {\n              // Don't use persona-clear-chat-button-wrapper class for top-right mode as its\n              // display: inline-flex causes alignment issues with the close button\n              clearChatButtonWrapper.className = \"persona-absolute persona-top-4 persona-z-50\";\n              // Position to the left of the close button (which is at right: 1rem/16px)\n              // Close button is ~32px wide, plus small gap = 48px from right\n              clearChatButtonWrapper.style.right = \"48px\";\n              container.style.position = \"relative\";\n              container.appendChild(clearChatButtonWrapper);\n            } else {\n              clearChatButtonWrapper.className = \"persona-relative persona-ml-auto persona-clear-chat-button-wrapper\";\n              // Clear the inline right style when switching back to inline mode\n              clearChatButtonWrapper.style.right = \"\";\n              // Find header and insert before close button\n              const header = container.querySelector(\".persona-border-b-persona-divider\");\n              const closeButtonWrapperEl = panelElements.closeButtonWrapper;\n              if (header && closeButtonWrapperEl && closeButtonWrapperEl.parentElement === header) {\n                header.insertBefore(clearChatButtonWrapper, closeButtonWrapperEl);\n              } else if (header) {\n                header.appendChild(clearChatButtonWrapper);\n              }\n            }\n\n            // Also update close button's ml-auto class based on clear chat position\n            const closeButtonWrapperEl = panelElements.closeButtonWrapper;\n            if (closeButtonWrapperEl && !closeButtonWrapperEl.classList.contains(\"persona-absolute\")) {\n              if (isTopRight) {\n                // Clear chat moved to top-right, close needs ml-auto\n                closeButtonWrapperEl.classList.add(\"persona-ml-auto\");\n              } else {\n                // Clear chat is inline, close doesn't need ml-auto\n                closeButtonWrapperEl.classList.remove(\"persona-ml-auto\");\n              }\n            }\n          }\n        }\n\n        if (shouldShowClearChat) {\n          // Update size: composer-bar mode owns its sizing (16px to match\n          // the close icon), so leave size alone there. Floating-launcher\n          // and other modes still honor `launcher.clearChat.size`.\n          if (!isComposerBar()) {\n            const clearChatSize = clearChatConfig.size ?? \"32px\";\n            clearChatButton.style.height = clearChatSize;\n            clearChatButton.style.width = clearChatSize;\n          }\n\n          // Update icon\n          const clearChatIconName = clearChatConfig.iconName ?? \"refresh-cw\";\n          const clearChatIconColor = clearChatConfig.iconColor ?? \"\";\n\n          clearChatButton.style.color =\n            clearChatIconColor || HEADER_THEME_CSS.actionIconColor;\n\n          // Clear existing icon and render new one. Composer-bar shrinks\n          // the icon to match its 16px button.\n          clearChatButton.innerHTML = \"\";\n          const clearChatIconSize = isComposerBar() ? \"14px\" : \"20px\";\n          const iconSvg = renderLucideIcon(clearChatIconName, clearChatIconSize, \"currentColor\", 2);\n          if (iconSvg) {\n            clearChatButton.appendChild(iconSvg);\n          }\n\n          // Update background color\n          if (clearChatConfig.backgroundColor) {\n            clearChatButton.style.backgroundColor = clearChatConfig.backgroundColor;\n            clearChatButton.classList.remove(\"hover:persona-bg-gray-100\");\n          } else {\n            clearChatButton.style.backgroundColor = \"\";\n            clearChatButton.classList.add(\"hover:persona-bg-gray-100\");\n          }\n\n          // Update border\n          if (clearChatConfig.borderWidth || clearChatConfig.borderColor) {\n            const borderWidth = clearChatConfig.borderWidth || \"0px\";\n            const borderColor = clearChatConfig.borderColor || \"transparent\";\n            clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;\n            clearChatButton.classList.remove(\"persona-border-none\");\n          } else {\n            clearChatButton.style.border = \"\";\n            clearChatButton.classList.add(\"persona-border-none\");\n          }\n\n          // Update border radius\n          if (clearChatConfig.borderRadius) {\n            clearChatButton.style.borderRadius = clearChatConfig.borderRadius;\n            clearChatButton.classList.remove(\"persona-rounded-full\");\n          } else {\n            clearChatButton.style.borderRadius = \"\";\n            clearChatButton.classList.add(\"persona-rounded-full\");\n          }\n\n          // Update padding\n          if (clearChatConfig.paddingX) {\n            clearChatButton.style.paddingLeft = clearChatConfig.paddingX;\n            clearChatButton.style.paddingRight = clearChatConfig.paddingX;\n          } else {\n            clearChatButton.style.paddingLeft = \"\";\n            clearChatButton.style.paddingRight = \"\";\n          }\n          if (clearChatConfig.paddingY) {\n            clearChatButton.style.paddingTop = clearChatConfig.paddingY;\n            clearChatButton.style.paddingBottom = clearChatConfig.paddingY;\n          } else {\n            clearChatButton.style.paddingTop = \"\";\n            clearChatButton.style.paddingBottom = \"\";\n          }\n\n          const clearChatTooltipText = clearChatConfig.tooltipText ?? \"Clear chat\";\n          const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;\n\n          clearChatButton.setAttribute(\"aria-label\", clearChatTooltipText);\n\n          if (clearChatButtonWrapper) {\n            // Clean up old tooltip event listeners if they exist\n            if ((clearChatButtonWrapper as any)._cleanupTooltip) {\n              (clearChatButtonWrapper as any)._cleanupTooltip();\n              delete (clearChatButtonWrapper as any)._cleanupTooltip;\n            }\n\n            // Set up new portaled tooltip with event listeners\n            if (clearChatShowTooltip && clearChatTooltipText) {\n              let portaledTooltip: HTMLElement | null = null;\n\n              const showTooltip = () => {\n                if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist\n\n                const tooltipDocument = clearChatButton.ownerDocument;\n                const tooltipContainer = tooltipDocument.body;\n                if (!tooltipContainer) return;\n\n                // Create tooltip element\n                portaledTooltip = createElementInDocument(\n                  tooltipDocument,\n                  \"div\",\n                  \"persona-clear-chat-tooltip\"\n                );\n                portaledTooltip.textContent = clearChatTooltipText;\n\n                // Add arrow\n                const arrow = createElementInDocument(tooltipDocument, \"div\");\n                arrow.className = \"persona-clear-chat-tooltip-arrow\";\n                portaledTooltip.appendChild(arrow);\n\n                // Get button position\n                const buttonRect = clearChatButton.getBoundingClientRect();\n\n                // Position tooltip above button\n                portaledTooltip.style.position = \"fixed\";\n                portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);\n                portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;\n                portaledTooltip.style.top = `${buttonRect.top - 8}px`;\n                portaledTooltip.style.transform = \"translate(-50%, -100%)\";\n\n                // Append to body\n                tooltipContainer.appendChild(portaledTooltip);\n              };\n\n              const hideTooltip = () => {\n                if (portaledTooltip && portaledTooltip.parentNode) {\n                  portaledTooltip.parentNode.removeChild(portaledTooltip);\n                  portaledTooltip = null;\n                }\n              };\n\n              // Add event listeners\n              clearChatButtonWrapper.addEventListener(\"mouseenter\", showTooltip);\n              clearChatButtonWrapper.addEventListener(\"mouseleave\", hideTooltip);\n              clearChatButton.addEventListener(\"focus\", showTooltip);\n              clearChatButton.addEventListener(\"blur\", hideTooltip);\n\n              // Store cleanup function on the button for later use\n              (clearChatButtonWrapper as any)._cleanupTooltip = () => {\n                hideTooltip();\n                if (clearChatButtonWrapper) {\n                  clearChatButtonWrapper.removeEventListener(\"mouseenter\", showTooltip);\n                  clearChatButtonWrapper.removeEventListener(\"mouseleave\", hideTooltip);\n                }\n                if (clearChatButton) {\n                  clearChatButton.removeEventListener(\"focus\", showTooltip);\n                  clearChatButton.removeEventListener(\"blur\", hideTooltip);\n                }\n              };\n            }\n          }\n        }\n      }\n\n      const nextParsers =\n        config.actionParsers && config.actionParsers.length\n          ? config.actionParsers\n          : [defaultJsonActionParser];\n      const nextHandlers =\n        config.actionHandlers && config.actionHandlers.length\n          ? config.actionHandlers\n          : [defaultActionHandlers.message, defaultActionHandlers.messageAndClick];\n\n      actionManager = createActionManager({\n        parsers: nextParsers,\n        handlers: nextHandlers,\n        getSessionMetadata,\n        updateSessionMetadata,\n        emit: eventBus.emit,\n        documentRef: typeof document !== \"undefined\" ? document : null\n      });\n\n      postprocess = buildPostprocessor(config, actionManager, handleResubmitRequested);\n      session.updateConfig(config);\n      renderMessagesWithPlugins(\n        messagesWrapper,\n        session.getMessages(),\n        postprocess\n      );\n      renderSuggestions();\n      updateCopy();\n      setComposerDisabled(session.isStreaming());\n      \n      // Update voice recognition mic button visibility\n      const voiceRecognitionEnabled = config.voiceRecognition?.enabled === true;\n      const hasSpeechRecognition =\n        typeof window !== 'undefined' &&\n        (typeof (window as any).webkitSpeechRecognition !== 'undefined' ||\n         typeof (window as any).SpeechRecognition !== 'undefined');\n      const hasRuntypeProvider =\n        config.voiceRecognition?.provider?.type === 'runtype';\n      const hasVoiceInput = hasSpeechRecognition || hasRuntypeProvider;\n\n      if (voiceRecognitionEnabled && hasVoiceInput) {\n        // Create or update mic button\n        if (!micButton || !micButtonWrapper) {\n          // Create new mic button\n          const micButtonResult = createMicButton(config.voiceRecognition, config.sendButton);\n          if (micButtonResult) {\n            // Update the mutable references\n            micButton = micButtonResult.micButton;\n            micButtonWrapper = micButtonResult.micButtonWrapper;\n            \n            // Insert into right actions before send button wrapper\n            rightActions.insertBefore(micButtonWrapper, sendButtonWrapper);\n            \n            // Wire up click handler\n            micButton.addEventListener(\"click\", handleMicButtonClick);\n            \n            // Set disabled state\n            micButton.disabled = session.isStreaming();\n          }\n        } else {\n          // Update existing mic button with new config\n          const voiceConfig = config.voiceRecognition ?? {};\n          const sendButtonConfig = config.sendButton ?? {};\n          \n          // Update icon name and size\n          const micIconName = voiceConfig.iconName ?? \"mic\";\n          const buttonSize = sendButtonConfig.size ?? \"40px\";\n          const micIconSize = voiceConfig.iconSize ?? buttonSize;\n          const micIconSizeNum = parseFloat(micIconSize) || 24;\n          \n          micButton.style.width = micIconSize;\n          micButton.style.height = micIconSize;\n          micButton.style.minWidth = micIconSize;\n          micButton.style.minHeight = micIconSize;\n          \n          // Update icon\n          const iconColor = voiceConfig.iconColor ?? sendButtonConfig.textColor ?? \"currentColor\";\n          micButton.innerHTML = \"\";\n          const micIconSvg = renderLucideIcon(micIconName, micIconSizeNum, iconColor, 2);\n          if (micIconSvg) {\n            micButton.appendChild(micIconSvg);\n          } else {\n            micButton.textContent = \"🎤\";\n          }\n          \n          // Update colors from config or theme tokens\n          const backgroundColor = voiceConfig.backgroundColor ?? sendButtonConfig.backgroundColor;\n          if (backgroundColor) {\n            micButton.style.backgroundColor = backgroundColor;\n          } else {\n            micButton.style.backgroundColor = \"\";\n          }\n\n          if (iconColor) {\n            micButton.style.color = iconColor;\n          } else {\n            micButton.style.color = \"var(--persona-text, #111827)\";\n          }\n          \n          // Update border styling\n          if (voiceConfig.borderWidth) {\n            micButton.style.borderWidth = voiceConfig.borderWidth;\n            micButton.style.borderStyle = \"solid\";\n          } else {\n            micButton.style.borderWidth = \"\";\n            micButton.style.borderStyle = \"\";\n          }\n          if (voiceConfig.borderColor) {\n            micButton.style.borderColor = voiceConfig.borderColor;\n          } else {\n            micButton.style.borderColor = \"\";\n          }\n          \n          // Update padding styling\n          if (voiceConfig.paddingX) {\n            micButton.style.paddingLeft = voiceConfig.paddingX;\n            micButton.style.paddingRight = voiceConfig.paddingX;\n          } else {\n            micButton.style.paddingLeft = \"\";\n            micButton.style.paddingRight = \"\";\n          }\n          if (voiceConfig.paddingY) {\n            micButton.style.paddingTop = voiceConfig.paddingY;\n            micButton.style.paddingBottom = voiceConfig.paddingY;\n          } else {\n            micButton.style.paddingTop = \"\";\n            micButton.style.paddingBottom = \"\";\n          }\n          \n          // Update tooltip\n          const tooltip = micButtonWrapper?.querySelector(\".persona-send-button-tooltip\") as HTMLElement | null;\n          const tooltipText = voiceConfig.tooltipText ?? \"Start voice recognition\";\n          const showTooltip = voiceConfig.showTooltip ?? false;\n          if (showTooltip && tooltipText) {\n            if (!tooltip) {\n              // Create tooltip if it doesn't exist\n              const newTooltip = document.createElement(\"div\");\n              newTooltip.className = \"persona-send-button-tooltip\";\n              newTooltip.textContent = tooltipText;\n              micButtonWrapper?.insertBefore(newTooltip, micButton);\n            } else {\n              tooltip.textContent = tooltipText;\n              tooltip.style.display = \"\";\n            }\n          } else if (tooltip) {\n            // Hide tooltip if disabled\n            tooltip.style.display = \"none\";\n          }\n          \n          // Show and update disabled state\n          micButtonWrapper.style.display = \"\";\n          micButton.disabled = session.isStreaming();\n        }\n      } else {\n        // Hide mic button\n        if (micButton && micButtonWrapper) {\n          micButtonWrapper.style.display = \"none\";\n          // Stop any active recording if disabling\n          if (config.voiceRecognition?.provider?.type === 'runtype') {\n            if (session.isVoiceActive()) session.toggleVoice();\n          } else if (isRecording) {\n            stopVoiceRecognition();\n          }\n        }\n      }\n\n      // Update attachment button visibility based on attachments config\n      const attachmentsEnabled = config.attachments?.enabled === true;\n      if (attachmentsEnabled) {\n        // Create or show attachment button\n        if (!attachmentButtonWrapper || !attachmentButton) {\n          // Need to create the attachment elements dynamically\n          const attachmentsConfig = config.attachments ?? {};\n          const sendButtonConfig = config.sendButton ?? {};\n          const buttonSize = sendButtonConfig.size ?? \"40px\";\n\n          // Create previews container if not exists\n          if (!attachmentPreviewsContainer) {\n            attachmentPreviewsContainer = createElement(\"div\", \"persona-attachment-previews persona-flex persona-flex-wrap persona-gap-2 persona-mb-2\");\n            attachmentPreviewsContainer.style.display = \"none\";\n            composerForm.insertBefore(attachmentPreviewsContainer, textarea);\n          }\n\n          // Create file input if not exists\n          if (!attachmentInput) {\n            attachmentInput = document.createElement(\"input\");\n            attachmentInput.type = \"file\";\n            attachmentInput.accept = (attachmentsConfig.allowedTypes ?? ALL_SUPPORTED_MIME_TYPES).join(\",\");\n            attachmentInput.multiple = (attachmentsConfig.maxFiles ?? 4) > 1;\n            attachmentInput.style.display = \"none\";\n            attachmentInput.setAttribute(\"aria-label\", \"Attach files\");\n            composerForm.insertBefore(attachmentInput, textarea);\n          }\n\n          // Create attachment button wrapper\n          attachmentButtonWrapper = createElement(\"div\", \"persona-send-button-wrapper\");\n\n          // Create attachment button\n          attachmentButton = createElement(\n            \"button\",\n            \"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer persona-attachment-button\"\n          ) as HTMLButtonElement;\n          attachmentButton.type = \"button\";\n          attachmentButton.setAttribute(\"aria-label\", attachmentsConfig.buttonTooltipText ?? \"Attach file\");\n\n          // Default to paperclip icon\n          const attachIconName = attachmentsConfig.buttonIconName ?? \"paperclip\";\n          const attachIconSize = buttonSize;\n          const buttonSizeNum = parseFloat(attachIconSize) || 40;\n          // Icon should be ~60% of button size to match other icons visually\n          const attachIconSizeNum = Math.round(buttonSizeNum * 0.6);\n\n          attachmentButton.style.width = attachIconSize;\n          attachmentButton.style.height = attachIconSize;\n          attachmentButton.style.minWidth = attachIconSize;\n          attachmentButton.style.minHeight = attachIconSize;\n          attachmentButton.style.fontSize = \"18px\";\n          attachmentButton.style.lineHeight = \"1\";\n          attachmentButton.style.backgroundColor = \"transparent\";\n          attachmentButton.style.color = \"var(--persona-primary, #111827)\";\n          attachmentButton.style.border = \"none\";\n          attachmentButton.style.borderRadius = \"6px\";\n          attachmentButton.style.transition = \"background-color 0.15s ease\";\n\n          // Add hover effect via mouseenter/mouseleave\n          attachmentButton.addEventListener(\"mouseenter\", () => {\n            attachmentButton!.style.backgroundColor = \"var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.05))\";\n          });\n          attachmentButton.addEventListener(\"mouseleave\", () => {\n            attachmentButton!.style.backgroundColor = \"transparent\";\n          });\n\n          const attachIconSvg = renderLucideIcon(attachIconName, attachIconSizeNum, \"currentColor\", 1.5);\n          if (attachIconSvg) {\n            attachmentButton.appendChild(attachIconSvg);\n          } else {\n            attachmentButton.textContent = \"📎\";\n          }\n\n          attachmentButton.addEventListener(\"click\", (e) => {\n            e.preventDefault();\n            attachmentInput?.click();\n          });\n\n          attachmentButtonWrapper.appendChild(attachmentButton);\n\n          // Add tooltip\n          const attachTooltipText = attachmentsConfig.buttonTooltipText ?? \"Attach file\";\n          const tooltip = createElement(\"div\", \"persona-send-button-tooltip\");\n          tooltip.textContent = attachTooltipText;\n          attachmentButtonWrapper.appendChild(tooltip);\n\n          // Insert into left actions container\n          leftActions.append(attachmentButtonWrapper);\n\n          // Initialize attachment manager\n          if (!attachmentManager && attachmentInput && attachmentPreviewsContainer) {\n            attachmentManager = AttachmentManager.fromConfig(attachmentsConfig);\n            attachmentManager.setPreviewsContainer(attachmentPreviewsContainer);\n\n            attachmentInput.addEventListener(\"change\", async () => {\n              if (attachmentManager && attachmentInput?.files) {\n                await attachmentManager.handleFileSelect(attachmentInput.files);\n                attachmentInput.value = \"\";\n              }\n            });\n          }\n\n          // Create drop overlay if missing\n          if (!container.querySelector(\".persona-attachment-drop-overlay\")) {\n            container.appendChild(buildDropOverlay(attachmentsConfig.dropOverlay));\n          }\n        } else {\n          // Show existing attachment button and update config\n          attachmentButtonWrapper.style.display = \"\";\n\n          // Update file input accept attribute when config changes\n          const attachmentsConfig = config.attachments ?? {};\n          if (attachmentInput) {\n            attachmentInput.accept = (attachmentsConfig.allowedTypes ?? ALL_SUPPORTED_MIME_TYPES).join(\",\");\n            attachmentInput.multiple = (attachmentsConfig.maxFiles ?? 4) > 1;\n          }\n\n          // Update attachment manager config\n          if (attachmentManager) {\n            attachmentManager.updateConfig({\n              allowedTypes: attachmentsConfig.allowedTypes,\n              maxFileSize: attachmentsConfig.maxFileSize,\n              maxFiles: attachmentsConfig.maxFiles\n            });\n          }\n        }\n      } else {\n        // Hide attachment button if disabled\n        if (attachmentButtonWrapper) {\n          attachmentButtonWrapper.style.display = \"none\";\n        }\n        // Clear any pending attachments\n        if (attachmentManager) {\n          attachmentManager.clearAttachments();\n        }\n        // Remove drop overlay\n        container.querySelector(\".persona-attachment-drop-overlay\")?.remove();\n      }\n\n      // Update send button styling\n      const sendButtonConfig = config.sendButton ?? {};\n      const useIcon = sendButtonConfig.useIcon ?? false;\n      const iconText = sendButtonConfig.iconText ?? \"↑\";\n      const iconName = sendButtonConfig.iconName;\n      const tooltipText = sendButtonConfig.tooltipText ?? \"Send message\";\n      const showTooltip = sendButtonConfig.showTooltip ?? false;\n      const buttonSize = sendButtonConfig.size ?? \"40px\";\n      const backgroundColor = sendButtonConfig.backgroundColor;\n      const textColor = sendButtonConfig.textColor;\n\n      // Update button content and styling based on mode\n      if (useIcon) {\n        // Icon mode: circular button\n        sendButton.style.width = buttonSize;\n        sendButton.style.height = buttonSize;\n        sendButton.style.minWidth = buttonSize;\n        sendButton.style.minHeight = buttonSize;\n        sendButton.style.fontSize = \"18px\";\n        sendButton.style.lineHeight = \"1\";\n        \n        // Clear existing content\n        sendButton.innerHTML = \"\";\n        \n        // Set foreground color from config or theme token\n        if (textColor) {\n          sendButton.style.color = textColor;\n        } else {\n          sendButton.style.color = \"var(--persona-button-primary-fg, #ffffff)\";\n        }\n\n        // Use Lucide icon if iconName is provided, otherwise fall back to iconText\n        if (iconName) {\n          const iconSize = parseFloat(buttonSize) || 24;\n          const iconColor = textColor?.trim() || \"currentColor\";\n          const iconSvg = renderLucideIcon(iconName, iconSize, iconColor, 2);\n          if (iconSvg) {\n            sendButton.appendChild(iconSvg);\n          } else {\n            sendButton.textContent = iconText;\n          }\n        } else {\n          sendButton.textContent = iconText;\n        }\n        \n        // Update classes\n        sendButton.className = \"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer\";\n        \n        if (backgroundColor) {\n          sendButton.style.backgroundColor = backgroundColor;\n          sendButton.classList.remove(\"persona-bg-persona-primary\");\n        } else {\n          sendButton.style.backgroundColor = \"\";\n          sendButton.classList.add(\"persona-bg-persona-primary\");\n        }\n      } else {\n        // Text mode: existing behavior\n        sendButton.textContent = config.copy?.sendButtonLabel ?? \"Send\";\n        sendButton.style.width = \"\";\n        sendButton.style.height = \"\";\n        sendButton.style.minWidth = \"\";\n        sendButton.style.minHeight = \"\";\n        sendButton.style.fontSize = \"\";\n        sendButton.style.lineHeight = \"\";\n        \n        // Update classes\n        sendButton.className = \"persona-rounded-button persona-bg-persona-accent persona-px-4 persona-py-2 persona-text-sm persona-font-semibold persona-text-white disabled:persona-opacity-50 persona-cursor-pointer\";\n        \n        if (backgroundColor) {\n          sendButton.style.backgroundColor = backgroundColor;\n          sendButton.classList.remove(\"persona-bg-persona-accent\");\n        } else {\n          sendButton.classList.add(\"persona-bg-persona-accent\");\n        }\n        \n        if (textColor) {\n          sendButton.style.color = textColor;\n        } else {\n          sendButton.classList.add(\"persona-text-white\");\n        }\n      }\n\n      // Apply border styling\n      if (sendButtonConfig.borderWidth) {\n        sendButton.style.borderWidth = sendButtonConfig.borderWidth;\n        sendButton.style.borderStyle = \"solid\";\n      } else {\n        sendButton.style.borderWidth = \"\";\n        sendButton.style.borderStyle = \"\";\n      }\n      if (sendButtonConfig.borderColor) {\n        sendButton.style.borderColor = sendButtonConfig.borderColor;\n      } else {\n        sendButton.style.borderColor = \"\";\n      }\n\n      // Apply padding styling (works in both icon and text mode)\n      if (sendButtonConfig.paddingX) {\n        sendButton.style.paddingLeft = sendButtonConfig.paddingX;\n        sendButton.style.paddingRight = sendButtonConfig.paddingX;\n      } else {\n        sendButton.style.paddingLeft = \"\";\n        sendButton.style.paddingRight = \"\";\n      }\n      if (sendButtonConfig.paddingY) {\n        sendButton.style.paddingTop = sendButtonConfig.paddingY;\n        sendButton.style.paddingBottom = sendButtonConfig.paddingY;\n      } else {\n        sendButton.style.paddingTop = \"\";\n        sendButton.style.paddingBottom = \"\";\n      }\n\n      // Update tooltip\n      const tooltip = sendButtonWrapper?.querySelector(\".persona-send-button-tooltip\") as HTMLElement | null;\n      if (showTooltip && tooltipText) {\n        if (!tooltip) {\n          // Create tooltip if it doesn't exist\n          const newTooltip = document.createElement(\"div\");\n          newTooltip.className = \"persona-send-button-tooltip\";\n          newTooltip.textContent = tooltipText;\n          sendButtonWrapper?.insertBefore(newTooltip, sendButton);\n        } else {\n          tooltip.textContent = tooltipText;\n          tooltip.style.display = \"\";\n        }\n      } else if (tooltip) {\n        tooltip.style.display = \"none\";\n      }\n      \n      // Update contentMaxWidth on messages wrapper and composer. Same\n      // composer-bar fallback as the initial read above.\n      const updatedContentMaxWidth =\n        config.layout?.contentMaxWidth ??\n        (isComposerBar()\n          ? config.launcher?.composerBar?.contentMaxWidth ?? \"720px\"\n          : undefined);\n      if (updatedContentMaxWidth) {\n        messagesWrapper.style.maxWidth = updatedContentMaxWidth;\n        messagesWrapper.style.marginLeft = \"auto\";\n        messagesWrapper.style.marginRight = \"auto\";\n        messagesWrapper.style.width = \"100%\";\n        if (composerForm) {\n          composerForm.style.maxWidth = updatedContentMaxWidth;\n          composerForm.style.marginLeft = \"auto\";\n          composerForm.style.marginRight = \"auto\";\n        }\n        if (suggestions) {\n          suggestions.style.maxWidth = updatedContentMaxWidth;\n          suggestions.style.marginLeft = \"auto\";\n          suggestions.style.marginRight = \"auto\";\n        }\n      } else {\n        messagesWrapper.style.maxWidth = \"\";\n        messagesWrapper.style.marginLeft = \"\";\n        messagesWrapper.style.marginRight = \"\";\n        messagesWrapper.style.width = \"\";\n        if (composerForm) {\n          composerForm.style.maxWidth = \"\";\n          composerForm.style.marginLeft = \"\";\n          composerForm.style.marginRight = \"\";\n        }\n        if (suggestions) {\n          suggestions.style.maxWidth = \"\";\n          suggestions.style.marginLeft = \"\";\n          suggestions.style.marginRight = \"\";\n        }\n      }\n\n      // Update status indicator visibility and text\n      const statusIndicatorConfig = config.statusIndicator ?? {};\n      const isVisible = statusIndicatorConfig.visible ?? true;\n      statusText.style.display = isVisible ? \"\" : \"none\";\n      \n      // Update status text if status is currently set\n      if (session) {\n        const currentStatus = session.getStatus();\n        const getCurrentStatusText = (s: AgentWidgetSessionStatus): string => {\n          if (s === \"idle\") return statusIndicatorConfig.idleText ?? statusCopy.idle;\n          if (s === \"connecting\") return statusIndicatorConfig.connectingText ?? statusCopy.connecting;\n          if (s === \"connected\") return statusIndicatorConfig.connectedText ?? statusCopy.connected;\n          if (s === \"error\") return statusIndicatorConfig.errorText ?? statusCopy.error;\n          return statusCopy[s];\n        };\n        applyStatusToElement(statusText, getCurrentStatusText(currentStatus), statusIndicatorConfig, currentStatus);\n      }\n\n      // Update status text alignment\n      statusText.classList.remove(\"persona-text-left\", \"persona-text-center\", \"persona-text-right\");\n      const alignClass = statusIndicatorConfig.align === \"left\" ? \"persona-text-left\"\n        : statusIndicatorConfig.align === \"center\" ? \"persona-text-center\"\n        : \"persona-text-right\";\n      statusText.classList.add(alignClass);\n    },\n    open() {\n      if (!isPanelToggleable()) return;\n      setOpenState(true, \"api\");\n    },\n    close() {\n      if (!isPanelToggleable()) return;\n      setOpenState(false, \"api\");\n    },\n    toggle() {\n      if (!isPanelToggleable()) return;\n      setOpenState(!open, \"api\");\n    },\n    reconnect() {\n      session.reconnectNow();\n    },\n    clearChat() {\n      // Clear messages in session (this will trigger onMessagesChanged which re-renders)\n      artifactsPaneUserHidden = false;\n      session.clearMessages();\n      messageCache.clear();\n      resumeAutoScroll();\n\n      // Always clear the default localStorage key\n      try {\n        localStorage.removeItem(DEFAULT_CHAT_HISTORY_STORAGE_KEY);\n        if (config.debug) {\n          console.log(`[AgentWidget] Cleared default localStorage key: ${DEFAULT_CHAT_HISTORY_STORAGE_KEY}`);\n        }\n      } catch (error) {\n        console.error(\"[AgentWidget] Failed to clear default localStorage:\", error);\n      }\n\n      // Also clear custom localStorage key if configured\n      if (config.clearChatHistoryStorageKey && config.clearChatHistoryStorageKey !== DEFAULT_CHAT_HISTORY_STORAGE_KEY) {\n        try {\n          localStorage.removeItem(config.clearChatHistoryStorageKey);\n          if (config.debug) {\n            console.log(`[AgentWidget] Cleared custom localStorage key: ${config.clearChatHistoryStorageKey}`);\n          }\n        } catch (error) {\n          console.error(\"[AgentWidget] Failed to clear custom localStorage:\", error);\n        }\n      }\n\n      // Dispatch custom event for external handlers (e.g., localStorage clearing in examples)\n      const clearEvent = new CustomEvent(\"persona:clear-chat\", {\n        detail: { timestamp: new Date().toISOString() }\n      });\n      window.dispatchEvent(clearEvent);\n\n      if (storageAdapter?.clear) {\n        runStorageMutation(\n          () => storageAdapter.clear!(),\n          \"[AgentWidget] Failed to clear storage adapter:\"\n        );\n      }\n      persistentMetadata = {};\n      actionManager.syncFromMetadata();\n\n      // Clear event stream buffer and store, and reset throughput tracking\n      eventStreamBuffer?.clear();\n      throughputTracker?.reset();\n      eventStreamView?.update();\n    },\n    setMessage(message: string): boolean {\n      if (!textarea) return false;\n      if (session.isStreaming()) return false;\n      \n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      \n      textarea.value = message;\n      // Trigger input event for any listeners\n      textarea.dispatchEvent(new Event('input', { bubbles: true }));\n      return true;\n    },\n    submitMessage(message?: string): boolean {\n      if (session.isStreaming()) return false;\n      \n      const valueToSubmit = message?.trim() || textarea.value.trim();\n      if (!valueToSubmit) return false;\n      \n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      \n      textarea.value = \"\";\n      textarea.style.height = \"auto\"; // Reset height after clearing\n      session.sendMessage(valueToSubmit);\n      return true;\n    },\n    startVoiceRecognition(): boolean {\n      if (session.isStreaming()) return false;\n      if (config.voiceRecognition?.provider?.type === 'runtype') {\n        if (session.isVoiceActive()) return true;\n        if (!open && isPanelToggleable()) setOpenState(true, \"system\");\n        voiceState.manuallyDeactivated = false;\n        persistVoiceMetadata();\n        session.toggleVoice().then(() => {\n          voiceState.active = session.isVoiceActive();\n          emitVoiceState(\"user\");\n          if (session.isVoiceActive()) applyRuntypeMicRecordingStyles();\n        });\n        return true;\n      }\n      if (isRecording) return true;\n      const SpeechRecognitionClass = getSpeechRecognitionClass();\n      if (!SpeechRecognitionClass) return false;\n      if (!open && isPanelToggleable()) setOpenState(true, \"system\");\n      voiceState.manuallyDeactivated = false;\n      persistVoiceMetadata();\n      startVoiceRecognition(\"user\");\n      return true;\n    },\n    stopVoiceRecognition(): boolean {\n      if (config.voiceRecognition?.provider?.type === 'runtype') {\n        if (!session.isVoiceActive()) return false;\n        session.toggleVoice().then(() => {\n          voiceState.active = false;\n          voiceState.manuallyDeactivated = true;\n          persistVoiceMetadata();\n          emitVoiceState(\"user\");\n          removeRuntypeMicStateStyles();\n        });\n        return true;\n      }\n      if (!isRecording) return false;\n\n      voiceState.manuallyDeactivated = true;\n      persistVoiceMetadata();\n      stopVoiceRecognition(\"user\");\n      return true;\n    },\n    injectMessage(options: InjectMessageOptions): AgentWidgetMessage {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      return session.injectMessage(options);\n    },\n    injectAssistantMessage(options: InjectAssistantMessageOptions): AgentWidgetMessage {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      const result = session.injectAssistantMessage(options);\n\n      // Check if we should trigger resubmit after injection\n      // This handles the case where a handler returned resubmit: true and then\n      // injected a message - we wait until after injection to trigger resubmit\n      if (pendingResubmit) {\n        pendingResubmit = false;\n        if (pendingResubmitTimeout) {\n          clearTimeout(pendingResubmitTimeout);\n          pendingResubmitTimeout = null;\n        }\n        // Short delay to ensure message is in context\n        setTimeout(() => {\n          if (session && !session.isStreaming()) {\n            session.continueConversation();\n          }\n        }, 100);\n      }\n\n      return result;\n    },\n    injectUserMessage(options: InjectUserMessageOptions): AgentWidgetMessage {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      return session.injectUserMessage(options);\n    },\n    injectSystemMessage(options: InjectSystemMessageOptions): AgentWidgetMessage {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      return session.injectSystemMessage(options);\n    },\n    injectMessageBatch(optionsList: InjectMessageOptions[]): AgentWidgetMessage[] {\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      return session.injectMessageBatch(optionsList);\n    },\n    injectComponentDirective(\n      options: InjectComponentDirectiveOptions\n    ): AgentWidgetMessage {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      return session.injectComponentDirective(options);\n    },\n    /** @deprecated Use injectMessage() instead */\n    injectTestMessage(event: AgentWidgetEvent) {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      session.injectTestEvent(event);\n    },\n    async connectStream(\n      stream: ReadableStream<Uint8Array>,\n      options?: { assistantMessageId?: string }\n    ): Promise<void> {\n      return session.connectStream(stream, options);\n    },\n    /** Push a raw event into the event stream buffer (for testing/debugging) */\n    __pushEventStreamEvent(event: { type: string; payload: unknown }): void {\n      if (eventStreamBuffer) {\n        throughputTracker?.processEvent(event.type, event.payload);\n        eventStreamBuffer.push({\n          id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n          type: event.type,\n          timestamp: Date.now(),\n          payload: JSON.stringify(event.payload)\n        });\n      }\n    },\n    showEventStream(): void {\n      if (!showEventStreamToggle || !eventStreamBuffer) return;\n      toggleEventStreamOn();\n    },\n    hideEventStream(): void {\n      if (!eventStreamVisible) return;\n      toggleEventStreamOff();\n    },\n    isEventStreamVisible(): boolean {\n      return eventStreamVisible;\n    },\n    showArtifacts(): void {\n      if (!artifactsSidebarEnabled(config)) return;\n      artifactsPaneUserHidden = false;\n      artifactsPaneUserOpened = true;\n      syncArtifactPane();\n      artifactPaneApi?.setMobileOpen(true);\n    },\n    hideArtifacts(): void {\n      if (!artifactsSidebarEnabled(config)) return;\n      artifactsPaneUserHidden = true;\n      syncArtifactPane();\n    },\n    upsertArtifact(manual: PersonaArtifactManualUpsert): PersonaArtifactRecord | null {\n      if (!artifactsSidebarEnabled(config)) return null;\n      // Programmatic upserts match the streamed UX: only \"panel\"-mode\n      // artifacts auto-open the pane (overriding a previous Close), while\n      // \"card\"/\"inline\" stay calm — the injected transcript block is the\n      // affordance. Independent of `transcript: false`: pane-only callers\n      // (e.g. the theme editor preview) rely on the panel-default surfacing;\n      // callers that want the pane in a non-panel mode call showArtifacts().\n      const mode = resolveArtifactDisplayMode(\n        config.features?.artifacts,\n        manual.artifactType\n      );\n      if (mode === \"panel\") {\n        artifactsPaneUserHidden = false;\n        artifactsPaneUserOpened = true;\n      }\n      return session.upsertArtifact(manual);\n    },\n    selectArtifact(id: string): void {\n      if (!artifactsSidebarEnabled(config)) return;\n      session.selectArtifact(id);\n    },\n    clearArtifacts(): void {\n      if (!artifactsSidebarEnabled(config)) return;\n      session.clearArtifacts();\n    },\n    getArtifacts(): PersonaArtifactRecord[] {\n      return session?.getArtifacts() ?? [];\n    },\n    getSelectedArtifactId(): string | null {\n      return session?.getSelectedArtifactId() ?? null;\n    },\n    focusInput(): boolean {\n      // Composer-bar's textarea is always reachable in the collapsed pill,\n      // so don't gate focus behind `open` for that mode.\n      if (launcherEnabled && !open && !isComposerBar()) return false;\n      if (!textarea) return false;\n      textarea.focus();\n      return true;\n    },\n    async resolveApproval(\n      approvalId: string,\n      decision: 'approved' | 'denied',\n      options?: AgentWidgetApprovalDecisionOptions\n    ): Promise<void> {\n      const messages = session.getMessages();\n      const approvalMessage = messages.find(\n        m => m.variant === \"approval\" && m.approval?.id === approvalId\n      );\n      if (!approvalMessage?.approval) {\n        throw new Error(`Approval not found: ${approvalId}`);\n      }\n      // Mirror the in-panel click handler: WebMCP gate bubbles resolve a local\n      // Promise the bridge is parked on (no server round-trip and they carry an\n      // empty executionId/agentId), so they must NOT hit the server approval\n      // API. Route by the `toolType` marker set in `requestWebMcpApproval`.\n      if (approvalMessage.approval.toolType === \"webmcp\") {\n        session.resolveWebMcpApproval(approvalMessage.id, decision);\n        return;\n      }\n      return session.resolveApproval(approvalMessage.approval, decision, options);\n    },\n    getMessages() {\n      return session.getMessages();\n    },\n    getStatus() {\n      return session.getStatus();\n    },\n    getPersistentMetadata() {\n      return { ...persistentMetadata };\n    },\n    updatePersistentMetadata(\n      updater: (prev: Record<string, unknown>) => Record<string, unknown>\n    ) {\n      updateSessionMetadata(updater);\n    },\n    on(event, handler) {\n      return eventBus.on(event, handler);\n    },\n    off(event, handler) {\n      eventBus.off(event, handler);\n    },\n    // State query methods\n    isOpen(): boolean {\n      return isPanelToggleable() && open;\n    },\n    isVoiceActive(): boolean {\n      return voiceState.active;\n    },\n    /**\n     * Toggle \"Read aloud\" for an assistant message: play → pause → resume (or\n     * play → stop when the engine can't pause). Speaks via the configured\n     * speech engine (browser Web Speech API by default).\n     */\n    toggleReadAloud(messageId: string): void {\n      session.toggleReadAloud(messageId);\n    },\n    /** Stop any in-progress read-aloud / text-to-speech playback. */\n    stopReadAloud(): void {\n      session.stopSpeaking();\n    },\n    /** Current read-aloud playback state for a message (`idle` unless active). */\n    getReadAloudState(messageId: string): ReadAloudState {\n      return session.getReadAloudState(messageId);\n    },\n    /** Subscribe to read-aloud state changes. Returns an unsubscribe function. */\n    onReadAloudChange(\n      listener: (activeId: string | null, state: ReadAloudState) => void\n    ): () => void {\n      return session.onReadAloudChange(listener);\n    },\n    getState(): AgentWidgetStateSnapshot {\n      return {\n        open: isPanelToggleable() && open,\n        launcherEnabled,\n        voiceActive: voiceState.active,\n        streaming: session.isStreaming()\n      };\n    },\n    // Feedback methods (CSAT/NPS)\n    showCSATFeedback(options?: Partial<CSATFeedbackOptions>) {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      \n      // Remove any existing feedback forms\n      const existingFeedback = messagesWrapper.querySelector('.persona-feedback-container');\n      if (existingFeedback) {\n        existingFeedback.remove();\n      }\n      \n      const feedbackEl = createCSATFeedback({\n        onSubmit: async (rating, comment) => {\n          if (session.isClientTokenMode()) {\n            await session.submitCSATFeedback(rating, comment);\n          }\n          options?.onSubmit?.(rating, comment);\n        },\n        onDismiss: options?.onDismiss,\n        ...options,\n      });\n      \n      // Append to messages area at the bottom\n      messagesWrapper.appendChild(feedbackEl);\n      feedbackEl.scrollIntoView({ behavior: 'smooth', block: 'end' });\n    },\n    showNPSFeedback(options?: Partial<NPSFeedbackOptions>) {\n      // Auto-open widget if closed and the panel is toggleable\n      if (!open && isPanelToggleable()) {\n        setOpenState(true, \"system\");\n      }\n      \n      // Remove any existing feedback forms\n      const existingFeedback = messagesWrapper.querySelector('.persona-feedback-container');\n      if (existingFeedback) {\n        existingFeedback.remove();\n      }\n      \n      const feedbackEl = createNPSFeedback({\n        onSubmit: async (rating, comment) => {\n          if (session.isClientTokenMode()) {\n            await session.submitNPSFeedback(rating, comment);\n          }\n          options?.onSubmit?.(rating, comment);\n        },\n        onDismiss: options?.onDismiss,\n        ...options,\n      });\n      \n      // Append to messages area at the bottom\n      messagesWrapper.appendChild(feedbackEl);\n      feedbackEl.scrollIntoView({ behavior: 'smooth', block: 'end' });\n    },\n    async submitCSATFeedback(rating: number, comment?: string): Promise<void> {\n      return session.submitCSATFeedback(rating, comment);\n    },\n    async submitNPSFeedback(rating: number, comment?: string): Promise<void> {\n      return session.submitNPSFeedback(rating, comment);\n    },\n    destroy() {\n      // Commit the latest coalesced transcript while the live DOM and storage\n      // pipeline still exist, then let the normal teardown cancel any work\n      // scheduled by that final apply.\n      flushPendingStreamingText();\n      if (toolElapsedTimerId != null) {\n        clearInterval(toolElapsedTimerId);\n        toolElapsedTimerId = null;\n      }\n      destroyCallbacks.forEach((cb) => cb());\n      wrapper.remove();\n      pillRoot?.remove();\n      launcherButtonInstance?.destroy();\n      customLauncherElement?.remove();\n      if (closeHandler) {\n        closeButton.removeEventListener(\"click\", closeHandler);\n      }\n    }\n  };\n\n  const shouldExposeDebugApi =\n    (runtimeOptions?.debugTools ?? false) || Boolean(config.debug);\n\n  if (shouldExposeDebugApi && typeof window !== \"undefined\") {\n    const previousDebug = (window as any).AgentWidgetBrowser;\n    const debugApi = {\n      controller,\n      getMessages: controller.getMessages,\n      getStatus: controller.getStatus,\n      getMetadata: controller.getPersistentMetadata,\n      updateMetadata: controller.updatePersistentMetadata,\n      clearHistory: () => controller.clearChat(),\n      setVoiceActive: (active: boolean) =>\n        active\n          ? controller.startVoiceRecognition()\n          : controller.stopVoiceRecognition()\n    };\n    (window as any).AgentWidgetBrowser = debugApi;\n    destroyCallbacks.push(() => {\n      if ((window as any).AgentWidgetBrowser === debugApi) {\n        (window as any).AgentWidgetBrowser = previousDebug;\n      }\n    });\n  }\n\n  // ============================================================================\n  // INSTANCE-SCOPED WINDOW EVENTS FOR PROGRAMMATIC CONTROL\n  // ============================================================================\n  if (typeof window !== \"undefined\") {\n    const instanceId = mount.getAttribute(\"data-persona-instance\") || mount.id || \"persona-\" + Math.random().toString(36).slice(2, 8);\n\n    const handleFocusInput = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (!detail?.instanceId || detail.instanceId === instanceId) {\n        controller.focusInput();\n      }\n    };\n    window.addEventListener(\"persona:focusInput\", handleFocusInput);\n    destroyCallbacks.push(() => {\n      window.removeEventListener(\"persona:focusInput\", handleFocusInput);\n    });\n\n    if (showEventStreamToggle) {\n      const handleShowEvent = (e: Event) => {\n        const detail = (e as CustomEvent).detail;\n        if (!detail?.instanceId || detail.instanceId === instanceId) {\n          controller.showEventStream();\n        }\n      };\n      const handleHideEvent = (e: Event) => {\n        const detail = (e as CustomEvent).detail;\n        if (!detail?.instanceId || detail.instanceId === instanceId) {\n          controller.hideEventStream();\n        }\n      };\n      window.addEventListener(\"persona:showEventStream\", handleShowEvent);\n      window.addEventListener(\"persona:hideEventStream\", handleHideEvent);\n      destroyCallbacks.push(() => {\n        window.removeEventListener(\"persona:showEventStream\", handleShowEvent);\n        window.removeEventListener(\"persona:hideEventStream\", handleHideEvent);\n      });\n    }\n\n    const handleShowArtifacts = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (!detail?.instanceId || detail.instanceId === instanceId) {\n        controller.showArtifacts();\n      }\n    };\n    const handleHideArtifacts = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (!detail?.instanceId || detail.instanceId === instanceId) {\n        controller.hideArtifacts();\n      }\n    };\n    const handleUpsertArtifact = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (detail?.instanceId && detail.instanceId !== instanceId) return;\n      if (detail?.artifact) {\n        controller.upsertArtifact(detail.artifact as PersonaArtifactManualUpsert);\n      }\n    };\n    const handleSelectArtifact = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (detail?.instanceId && detail.instanceId !== instanceId) return;\n      if (typeof detail?.id === \"string\") {\n        controller.selectArtifact(detail.id);\n      }\n    };\n    const handleClearArtifacts = (e: Event) => {\n      const detail = (e as CustomEvent).detail;\n      if (!detail?.instanceId || detail.instanceId === instanceId) {\n        controller.clearArtifacts();\n      }\n    };\n    window.addEventListener(\"persona:showArtifacts\", handleShowArtifacts);\n    window.addEventListener(\"persona:hideArtifacts\", handleHideArtifacts);\n    window.addEventListener(\"persona:upsertArtifact\", handleUpsertArtifact);\n    window.addEventListener(\"persona:selectArtifact\", handleSelectArtifact);\n    window.addEventListener(\"persona:clearArtifacts\", handleClearArtifacts);\n    destroyCallbacks.push(() => {\n      window.removeEventListener(\"persona:showArtifacts\", handleShowArtifacts);\n      window.removeEventListener(\"persona:hideArtifacts\", handleHideArtifacts);\n      window.removeEventListener(\"persona:upsertArtifact\", handleUpsertArtifact);\n      window.removeEventListener(\"persona:selectArtifact\", handleSelectArtifact);\n      window.removeEventListener(\"persona:clearArtifacts\", handleClearArtifacts);\n    });\n  }\n\n  // ============================================================================\n  // STATE PERSISTENCE ACROSS PAGE NAVIGATIONS\n  // ============================================================================\n  const persistConfig = normalizePersistStateConfig(config.persistState);\n  \n  if (persistConfig && isPanelToggleable()) {\n    const storage = getPersistStorage(persistConfig.storage!);\n    const openKey = `${persistConfig.keyPrefix}widget-open`;\n    const voiceKey = `${persistConfig.keyPrefix}widget-voice`;\n    const voiceModeKey = `${persistConfig.keyPrefix}widget-voice-mode`;\n\n    if (storage) {\n      // Restore state from previous page\n      const wasOpen = persistConfig.persist?.openState && storage.getItem(openKey) === 'true';\n      const wasVoiceActive = persistConfig.persist?.voiceState && storage.getItem(voiceKey) === 'true';\n      // Also check if user was in voice mode (last message was via voice)\n      const wasInVoiceMode = persistConfig.persist?.voiceState && storage.getItem(voiceModeKey) === 'true';\n\n      if (wasOpen) {\n        // Use setTimeout to ensure DOM is ready\n        setTimeout(() => {\n          controller.open();\n\n          // After opening, restore input mode\n          setTimeout(() => {\n            // Restore voice if it was actively recording OR if user was in voice mode\n            if (wasVoiceActive || wasInVoiceMode) {\n              controller.startVoiceRecognition();\n            } else if (persistConfig.persist?.focusInput) {\n              const textarea = mount.querySelector('textarea') as HTMLTextAreaElement | null;\n              if (textarea) {\n                textarea.focus();\n              }\n            }\n          }, 100);\n        }, 0);\n      }\n\n      // Persist open/close state changes\n      if (persistConfig.persist?.openState) {\n        eventBus.on('widget:opened', () => {\n          storage.setItem(openKey, 'true');\n        });\n        eventBus.on('widget:closed', () => {\n          storage.setItem(openKey, 'false');\n        });\n      }\n\n      // Persist voice state changes\n      if (persistConfig.persist?.voiceState) {\n        eventBus.on('voice:state', (event) => {\n          storage.setItem(voiceKey, event.active ? 'true' : 'false');\n        });\n\n        // Persist whether user is in voice mode based on their messages\n        // This allows voice to resume after navigation even when recording was stopped for submission\n        eventBus.on('user:message', (message) => {\n          storage.setItem(voiceModeKey, message.viaVoice ? 'true' : 'false');\n        });\n      }\n\n      // Clear persisted state on chat clear\n      if (persistConfig.clearOnChatClear) {\n        const clearPersistState = () => {\n          storage.removeItem(openKey);\n          storage.removeItem(voiceKey);\n          storage.removeItem(voiceModeKey);\n        };\n\n        // Listen for clear chat event\n        const handleClearChat = () => clearPersistState();\n        window.addEventListener('persona:clear-chat', handleClearChat);\n\n        // Clean up listener on destroy\n        destroyCallbacks.push(() => {\n          window.removeEventListener('persona:clear-chat', handleClearChat);\n        });\n      }\n    }\n  }\n\n  // If onStateLoaded signalled open: true, open the panel after init.\n  // Mirrors the same setTimeout(0) pattern used by persistState restore so both\n  // can fire independently without interfering with each other.\n  if (shouldOpenAfterStateLoaded && isPanelToggleable()) {\n    setTimeout(() => { controller.open(); }, 0);\n  }\n\n  // Initial sync of the composer-bar peek banner so it reflects any\n  // restored history. Subsequent updates flow through `onMessagesChanged`,\n  // `onStreamingChanged`, `updateOpenState`, and pointerenter/leave on\n  // the panel.\n  syncComposerBarPeek();\n\n  // IIFE/CDN lazy path only: the parsers were not ready at mount, so any\n  // messages rendered so far (restored history, eager intro/injected messages)\n  // were escaped to plain text. Once the `markdown-parsers.js` chunk resolves,\n  // bust the message cache and re-render so they pick up real markdown. Bumping\n  // `configVersion` + clearing the cache is required because the message\n  // content is unchanged, so the fingerprint cache would otherwise reuse the\n  // stale escaped wrappers. `onMarkdownParsersReady` no-ops when the parsers are\n  // already loaded (the ESM build, and the CDN build after the first load), so\n  // the `markdownReadyAtInit` guard is redundant — kept only to skip the\n  // subscription bookkeeping on the common eager path.\n  if (!markdownReadyAtInit) {\n    const unsubscribeParsersReady = onMarkdownParsersReady(() => {\n      if (!session) return;\n      configVersion++;\n      messageCache.clear();\n      renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);\n    });\n    // Drop the subscription on teardown so a late chunk resolution can't clear\n    // the cache and render into a detached `messagesWrapper`.\n    destroyCallbacks.push(unsubscribeParsersReady);\n  }\n\n  return controller;\n};\n\nexport type AgentWidgetController = Controller;\n","import type {\n  AgentWidgetConfig,\n  AgentWidgetDockConfig,\n  AgentWidgetStateSnapshot,\n} from \"../types\";\nimport { isDockedMountMode, resolveDockConfig } from \"../utils/dock\";\nimport { DEFAULT_OVERLAY_Z_INDEX } from \"../utils/constants\";\nimport { createThemeObserver, getActiveTheme } from \"../utils/theme\";\nimport {\n  DEFAULT_PANEL_CANVAS_BACKGROUND,\n  DEFAULT_PANEL_INSET,\n  resolveTokenValue,\n} from \"../utils/tokens\";\n\nexport type WidgetHostLayoutMode = \"direct\" | \"docked\";\n\nexport type WidgetHostLayout = {\n  mode: WidgetHostLayoutMode;\n  host: HTMLElement;\n  shell: HTMLElement | null;\n  syncWidgetState: (state: Pick<AgentWidgetStateSnapshot, \"open\" | \"launcherEnabled\">) => void;\n  updateConfig: (config?: AgentWidgetConfig) => void;\n  destroy: () => void;\n};\n\n/** Parse `dock.width` for push layout math (px or % of shell). Fallback: 420. */\nconst parseDockWidthToPx = (width: string, shellClientWidth: number): number => {\n  const w = width.trim();\n  const px = /^(\\d+(?:\\.\\d+)?)px$/i.exec(w);\n  if (px) return Math.max(0, parseFloat(px[1]));\n  const pct = /^(\\d+(?:\\.\\d+)?)%$/i.exec(w);\n  if (pct) return Math.max(0, (shellClientWidth * parseFloat(pct[1])) / 100);\n  return 420;\n};\n\n/**\n * Viewport-overflow guard. The docked shell sizes itself with `height: 100%`,\n * which only resolves when an ancestor provides a definite height; without one\n * the dock column would grow with the conversation and scroll off the page.\n * Clamping the slot keeps the panel viewport-sized (messages scroll\n * internally) even when the page's height chain is missing. `100vh` is set\n * first as a fallback for engines without `dvh` support: an invalid value\n * leaves the previous one in place.\n */\nconst applyDockSlotMaxHeight = (\n  dockSlot: HTMLElement,\n  maxHeight: string | false\n): void => {\n  if (maxHeight === false) {\n    dockSlot.style.maxHeight = \"\";\n    return;\n  }\n  dockSlot.style.maxHeight = \"100vh\";\n  dockSlot.style.maxHeight = maxHeight;\n};\n\n/**\n * Sticky keeps the resize/emerge dock column pinned to the top of the viewport\n * when the surrounding page is taller than the screen (e.g. a missing height\n * chain or a deliberately scrolling page). With a properly sized shell it\n * behaves exactly like the previous `position: relative`. Not used for push:\n * push gets the max-height cap only, like overlay, since the dock slot is an\n * in-flow `position: relative` column there rather than a viewport-pinned one.\n * (The push track itself is offset with `margin-left`, not a transform, so it\n * no longer establishes a containing block for fixed/sticky descendants.)\n */\nconst applyInFlowDockSlotPosition = (\n  dockSlot: HTMLElement,\n  maxHeight: string | false\n): void => {\n  if (maxHeight === false) {\n    dockSlot.style.position = \"relative\";\n    dockSlot.style.top = \"\";\n  } else {\n    dockSlot.style.position = \"sticky\";\n    dockSlot.style.top = \"0\";\n  }\n};\n\n/**\n * Warns once per docked mount when no ancestor of the dock target provides a\n * definite height, which is the common misconfiguration behind a dock panel\n * that grows past the viewport. Probes with a throwaway `height: 100%` child\n * of the shell's parent; a fixed-height reference probe first checks the\n * environment can measure at all (display: none subtrees and jsdom measure\n * everything as 0, and must not produce a false warning).\n */\nconst warnIfDockHeightChainUnresolved = (\n  shell: HTMLElement,\n  dock: Required<AgentWidgetDockConfig>\n): void => {\n  const parent = shell.parentElement;\n  if (!parent) return;\n  const probe = shell.ownerDocument.createElement(\"div\");\n  probe.style.cssText =\n    \"width:0;height:1px;margin:0;padding:0;border:0;visibility:hidden;\";\n  parent.appendChild(probe);\n  const measurable = probe.offsetHeight > 0;\n  probe.style.height = \"100%\";\n  const resolved = probe.offsetHeight > 0;\n  probe.remove();\n  if (!measurable || resolved) return;\n  console.warn(\n    \"[AgentWidget] Docked mode: no ancestor of the dock target provides a definite height, \" +\n      \"so the dock panel cannot size to your layout.\" +\n      (dock.maxHeight === false\n        ? \" The viewport guard is disabled (dock.maxHeight: false), so the panel will grow with the conversation and overflow the viewport.\"\n        : ` Falling back to clamping the panel to ${dock.maxHeight} (configurable via launcher.dock.maxHeight).`) +\n      \" To size the panel from your layout instead, give the height chain a definite height \" +\n      \"(e.g. `html, body { height: 100% }`) down to the dock target's parent.\"\n  );\n};\n\nconst setDirectHostStyles = (host: HTMLElement, config?: AgentWidgetConfig): void => {\n  const launcherEnabled = config?.launcher?.enabled ?? true;\n  host.className = \"persona-host\";\n  host.style.height = launcherEnabled ? \"\" : \"100%\";\n  host.style.display = launcherEnabled ? \"\" : \"flex\";\n  host.style.flexDirection = launcherEnabled ? \"\" : \"column\";\n  host.style.flex = launcherEnabled ? \"\" : \"1 1 auto\";\n  host.style.minHeight = launcherEnabled ? \"\" : \"0\";\n};\n\nconst clearOverlayDockSlotStyles = (dockSlot: HTMLElement): void => {\n  dockSlot.style.position = \"\";\n  dockSlot.style.top = \"\";\n  dockSlot.style.bottom = \"\";\n  dockSlot.style.left = \"\";\n  dockSlot.style.right = \"\";\n  dockSlot.style.zIndex = \"\";\n  dockSlot.style.transform = \"\";\n  dockSlot.style.pointerEvents = \"\";\n};\n\n/** Clears viewport-escape fullscreen styles so reveal modes can re-apply dock layout. */\nconst clearMobileFullscreenDockSlotStyles = (dockSlot: HTMLElement): void => {\n  dockSlot.style.inset = \"\";\n  dockSlot.style.width = \"\";\n  dockSlot.style.height = \"\";\n  dockSlot.style.maxWidth = \"\";\n  dockSlot.style.maxHeight = \"\";\n  dockSlot.style.minWidth = \"\";\n  clearOverlayDockSlotStyles(dockSlot);\n};\n\nconst clearResizeDockSlotTransition = (dockSlot: HTMLElement): void => {\n  dockSlot.style.transition = \"\";\n};\n\nconst clearPushTrackStyles = (pushTrack: HTMLElement): void => {\n  pushTrack.style.display = \"\";\n  pushTrack.style.flexDirection = \"\";\n  pushTrack.style.flex = \"\";\n  pushTrack.style.minHeight = \"\";\n  pushTrack.style.minWidth = \"\";\n  pushTrack.style.width = \"\";\n  pushTrack.style.height = \"\";\n  pushTrack.style.alignItems = \"\";\n  pushTrack.style.transition = \"\";\n  pushTrack.style.transform = \"\";\n  pushTrack.style.marginLeft = \"\";\n};\n\nconst resetContentSlotFlexSizing = (contentSlot: HTMLElement): void => {\n  contentSlot.style.width = \"\";\n  contentSlot.style.maxWidth = \"\";\n  contentSlot.style.minWidth = \"\";\n  contentSlot.style.flex = \"1 1 auto\";\n};\n\nconst clearEmergeDockStyles = (host: HTMLElement, dockSlot: HTMLElement): void => {\n  host.style.width = \"\";\n  host.style.minWidth = \"\";\n  host.style.maxWidth = \"\";\n  host.style.boxSizing = \"\";\n  dockSlot.style.alignItems = \"\";\n};\n\nconst migrateDockChildren = (\n  shell: HTMLElement,\n  pushTrack: HTMLElement,\n  contentSlot: HTMLElement,\n  dockSlot: HTMLElement,\n  usePush: boolean\n): void => {\n  if (usePush) {\n    if (contentSlot.parentElement !== pushTrack) {\n      shell.replaceChildren();\n      pushTrack.replaceChildren(contentSlot, dockSlot);\n      shell.appendChild(pushTrack);\n    }\n  } else if (contentSlot.parentElement === pushTrack) {\n    pushTrack.replaceChildren();\n    shell.appendChild(contentSlot);\n    shell.appendChild(dockSlot);\n  }\n};\n\nconst orderDockChildren = (\n  shell: HTMLElement,\n  pushTrack: HTMLElement,\n  contentSlot: HTMLElement,\n  dockSlot: HTMLElement,\n  side: \"left\" | \"right\",\n  usePush: boolean\n): void => {\n  const parent = usePush ? pushTrack : shell;\n  if (side === \"left\") {\n    if (parent.firstElementChild !== dockSlot) {\n      parent.replaceChildren(dockSlot, contentSlot);\n    }\n  } else if (parent.lastElementChild !== dockSlot) {\n    parent.replaceChildren(contentSlot, dockSlot);\n  }\n};\n\n/**\n * Detached inset + canvas literals for the dockSlot. The panel CSS vars are set\n * inline on the widget root inside the slot, so they cannot cascade up to the\n * slot; resolve the token values here and apply literals.\n */\nconst resolveDetachedSlotStyles = (\n  config: AgentWidgetConfig | undefined\n): { inset: string; canvasBackground: string } => {\n  const theme = getActiveTheme(config);\n  const panel = theme.components?.panel;\n  const resolve = (raw: string | undefined, fallback: string): string => {\n    if (raw == null || raw === \"\") return fallback;\n    return resolveTokenValue(theme, raw) ?? raw;\n  };\n  return {\n    inset: resolve(panel?.inset, DEFAULT_PANEL_INSET),\n    canvasBackground: resolve(panel?.canvasBackground, DEFAULT_PANEL_CANVAS_BACKGROUND),\n  };\n};\n\ntype DetachedSlotStyles = { inset: string; canvasBackground: string };\n\n/**\n * Detached docked mode pads the slot so the canvas shows around the panel card.\n * Padding, never margin, keeps parseDockWidthToPx push math truthful; box-sizing\n * border-box keeps the slot outer width (and max-height) inclusive of the\n * padding so the panel shrinks inside rather than overflowing.\n */\nconst applyDetachedDockSlotChrome = (\n  dockSlot: HTMLElement,\n  detached: boolean,\n  inset: string,\n  canvasBackground: string\n): void => {\n  if (!detached) {\n    dockSlot.style.padding = \"\";\n    dockSlot.style.background = \"\";\n    dockSlot.style.boxSizing = \"\";\n    return;\n  }\n  dockSlot.style.boxSizing = \"border-box\";\n  dockSlot.style.padding = inset;\n  dockSlot.style.background = canvasBackground;\n};\n\nconst applyDockStyles = (\n  shell: HTMLElement,\n  pushTrack: HTMLElement,\n  contentSlot: HTMLElement,\n  dockSlot: HTMLElement,\n  host: HTMLElement,\n  config: AgentWidgetConfig | undefined,\n  expanded: boolean,\n  // Cached slot styles; null when not detached. Resolved once per config/scheme\n  // change so getActiveTheme never runs on the resize/ResizeObserver path.\n  detachedSlotStyles: DetachedSlotStyles | null\n): void => {\n  const dock = resolveDockConfig(config);\n  const usePush = dock.reveal === \"push\";\n  const detached = detachedSlotStyles != null;\n  const detachedInset = detachedSlotStyles?.inset ?? \"\";\n  const detachedCanvas = detachedSlotStyles?.canvasBackground ?? \"\";\n\n  migrateDockChildren(shell, pushTrack, contentSlot, dockSlot, usePush);\n  orderDockChildren(shell, pushTrack, contentSlot, dockSlot, dock.side, usePush);\n\n  shell.dataset.personaHostLayout = \"docked\";\n  shell.dataset.personaDockSide = dock.side;\n  shell.dataset.personaDockOpen = expanded ? \"true\" : \"false\";\n  shell.style.width = \"100%\";\n  shell.style.maxWidth = \"100%\";\n  shell.style.minWidth = \"0\";\n  shell.style.height = \"100%\";\n  shell.style.minHeight = \"0\";\n  shell.style.position = \"relative\";\n\n  contentSlot.style.display = \"flex\";\n  contentSlot.style.flexDirection = \"column\";\n  contentSlot.style.minHeight = \"0\";\n  contentSlot.style.position = \"relative\";\n\n  host.className = \"persona-host\";\n  host.style.height = \"100%\";\n  host.style.minHeight = \"0\";\n  host.style.display = \"flex\";\n  host.style.flexDirection = \"column\";\n  host.style.flex = \"1 1 auto\";\n\n  const ownerWindow = shell.ownerDocument.defaultView;\n  const mobileFullscreenEnabled = config?.launcher?.mobileFullscreen ?? true;\n  const mobileBreakpoint = config?.launcher?.mobileBreakpoint ?? 640;\n  const isMobileViewport =\n    ownerWindow != null ? ownerWindow.innerWidth <= mobileBreakpoint : false;\n  const useMobileFullscreen = mobileFullscreenEnabled && isMobileViewport && expanded;\n\n  if (useMobileFullscreen) {\n    shell.dataset.personaDockMobileFullscreen = \"true\";\n    shell.removeAttribute(\"data-persona-dock-reveal\");\n    clearPushTrackStyles(pushTrack);\n    clearResizeDockSlotTransition(dockSlot);\n    clearMobileFullscreenDockSlotStyles(dockSlot);\n    resetContentSlotFlexSizing(contentSlot);\n    clearEmergeDockStyles(host, dockSlot);\n    // Mobile fullscreen is flush regardless of detachedPanel.\n    applyDetachedDockSlotChrome(dockSlot, false, \"\", \"\");\n\n    shell.style.display = \"flex\";\n    shell.style.flexDirection = \"column\";\n    shell.style.alignItems = \"stretch\";\n    shell.style.overflow = \"hidden\";\n\n    contentSlot.style.flex = \"1 1 auto\";\n    contentSlot.style.width = \"100%\";\n    contentSlot.style.minWidth = \"0\";\n\n    dockSlot.style.display = \"flex\";\n    dockSlot.style.flexDirection = \"column\";\n    dockSlot.style.position = \"fixed\";\n    dockSlot.style.inset = \"0\";\n    dockSlot.style.width = \"100%\";\n    dockSlot.style.height = \"100%\";\n    dockSlot.style.maxWidth = \"100%\";\n    dockSlot.style.minWidth = \"0\";\n    dockSlot.style.minHeight = \"0\";\n    dockSlot.style.overflow = \"hidden\";\n    dockSlot.style.zIndex = String(config?.launcher?.zIndex ?? DEFAULT_OVERLAY_Z_INDEX);\n    dockSlot.style.transform = \"none\";\n    dockSlot.style.transition = \"none\";\n    dockSlot.style.pointerEvents = \"auto\";\n    dockSlot.style.flex = \"none\";\n\n    if (usePush) {\n      pushTrack.style.display = \"flex\";\n      pushTrack.style.flexDirection = \"column\";\n      pushTrack.style.width = \"100%\";\n      pushTrack.style.height = \"100%\";\n      pushTrack.style.minHeight = \"0\";\n      pushTrack.style.minWidth = \"0\";\n      pushTrack.style.flex = \"1 1 auto\";\n      pushTrack.style.alignItems = \"stretch\";\n      pushTrack.style.transform = \"none\";\n      // Reset the desktop push offset: this fullscreen path applies styles\n      // inline without going through clearPushTrackStyles, so a stale negative\n      // marginLeft from a prior expanded desktop render would shift the now\n      // width:100% track off-screen on mobile.\n      pushTrack.style.marginLeft = \"0\";\n      pushTrack.style.transition = \"none\";\n      contentSlot.style.flex = \"1 1 auto\";\n      contentSlot.style.width = \"100%\";\n      contentSlot.style.maxWidth = \"100%\";\n      contentSlot.style.minWidth = \"0\";\n    }\n\n    return;\n  }\n\n  shell.removeAttribute(\"data-persona-dock-mobile-fullscreen\");\n  clearMobileFullscreenDockSlotStyles(dockSlot);\n  applyDockSlotMaxHeight(dockSlot, dock.maxHeight);\n  // Chrome only when open: a collapsed detached slot with padding + border-box\n  // floors at 2*inset, painting a permanent canvas strip past the 0px width.\n  applyDetachedDockSlotChrome(dockSlot, detached && expanded, detachedInset, detachedCanvas);\n\n  if (dock.reveal === \"overlay\") {\n    shell.style.display = \"flex\";\n    shell.style.flexDirection = \"row\";\n    shell.style.alignItems = \"stretch\";\n    shell.style.overflow = \"hidden\";\n    shell.dataset.personaDockReveal = \"overlay\";\n    clearPushTrackStyles(pushTrack);\n    clearResizeDockSlotTransition(dockSlot);\n    resetContentSlotFlexSizing(contentSlot);\n    clearEmergeDockStyles(host, dockSlot);\n\n    const dockTransition = dock.animate ? \"transform 180ms ease\" : \"none\";\n    const translateClosed = dock.side === \"right\" ? \"translateX(100%)\" : \"translateX(-100%)\";\n    const translate = expanded ? \"translateX(0)\" : translateClosed;\n\n    dockSlot.style.display = \"flex\";\n    dockSlot.style.flexDirection = \"column\";\n    dockSlot.style.flex = \"none\";\n    dockSlot.style.position = \"absolute\";\n    dockSlot.style.top = \"0\";\n    dockSlot.style.bottom = \"0\";\n    dockSlot.style.width = dock.width;\n    dockSlot.style.maxWidth = dock.width;\n    dockSlot.style.minWidth = dock.width;\n    dockSlot.style.minHeight = \"0\";\n    dockSlot.style.overflow = \"hidden\";\n    dockSlot.style.transition = dockTransition;\n    dockSlot.style.transform = translate;\n    dockSlot.style.pointerEvents = expanded ? \"auto\" : \"none\";\n    dockSlot.style.zIndex = \"2\";\n    if (dock.side === \"right\") {\n      dockSlot.style.right = \"0\";\n      dockSlot.style.left = \"\";\n    } else {\n      dockSlot.style.left = \"0\";\n      dockSlot.style.right = \"\";\n    }\n  } else if (dock.reveal === \"push\") {\n    // Row flex so the wide push track is laid out on the horizontal axis; column was stretching\n    // the track to the shell width and fighting explicit width, which could confuse overflow.\n    shell.style.display = \"flex\";\n    shell.style.flexDirection = \"row\";\n    shell.style.alignItems = \"stretch\";\n    shell.style.overflow = \"hidden\";\n    shell.dataset.personaDockReveal = \"push\";\n    clearResizeDockSlotTransition(dockSlot);\n    clearOverlayDockSlotStyles(dockSlot);\n    clearEmergeDockStyles(host, dockSlot);\n\n    const panelPx = parseDockWidthToPx(dock.width, shell.clientWidth);\n    const contentPx = Math.max(0, shell.clientWidth);\n    const dockTransition = dock.animate ? \"margin-left 180ms ease\" : \"none\";\n    // Slide the wide track with a negative left margin rather than a CSS\n    // transform. A `transform` (even `translateX(0)`) turns the push track into\n    // the containing block for any `position: fixed` descendant, so host pages\n    // that render viewport-fixed chrome inside the pushed content (e.g. the\n    // dashboard editor's `fixed top-0 right-0` toolbar) resolve `right: 0`\n    // against the track's right edge: `panelPx` past the viewport, off-screen.\n    // `margin-left` produces the identical visual offset (the track is clipped\n    // by the overflow:hidden shell) without establishing a containing block for\n    // fixed OR absolutely-positioned descendants.\n    const marginOffsetPx =\n      dock.side === \"right\"\n        ? expanded\n          ? -panelPx\n          : 0\n        : expanded\n          ? 0\n          : -panelPx;\n\n    pushTrack.style.display = \"flex\";\n    pushTrack.style.flexDirection = \"row\";\n    pushTrack.style.flex = \"0 0 auto\";\n    pushTrack.style.minHeight = \"0\";\n    pushTrack.style.minWidth = \"0\";\n    pushTrack.style.alignItems = \"stretch\";\n    pushTrack.style.height = \"100%\";\n    pushTrack.style.width = `${contentPx + panelPx}px`;\n    pushTrack.style.transition = dockTransition;\n    pushTrack.style.marginLeft = `${marginOffsetPx}px`;\n    // Defensively clear any transform a previous reveal mode may have set:    // leaving one would re-establish the fixed-position containing block.\n    pushTrack.style.transform = \"\";\n\n    contentSlot.style.flex = \"0 0 auto\";\n    contentSlot.style.flexGrow = \"0\";\n    contentSlot.style.flexShrink = \"0\";\n    contentSlot.style.width = `${contentPx}px`;\n    contentSlot.style.maxWidth = `${contentPx}px`;\n    contentSlot.style.minWidth = `${contentPx}px`;\n\n    dockSlot.style.display = \"flex\";\n    dockSlot.style.flexDirection = \"column\";\n    dockSlot.style.flex = \"0 0 auto\";\n    dockSlot.style.flexShrink = \"0\";\n    dockSlot.style.width = dock.width;\n    dockSlot.style.minWidth = dock.width;\n    dockSlot.style.maxWidth = dock.width;\n    dockSlot.style.position = \"relative\";\n    dockSlot.style.top = \"\";\n    dockSlot.style.overflow = \"hidden\";\n    dockSlot.style.transition = \"none\";\n    dockSlot.style.pointerEvents = expanded ? \"auto\" : \"none\";\n  } else {\n    shell.style.display = \"flex\";\n    shell.style.flexDirection = \"row\";\n    shell.style.alignItems = \"stretch\";\n    shell.style.overflow = \"\";\n    clearPushTrackStyles(pushTrack);\n    clearOverlayDockSlotStyles(dockSlot);\n    resetContentSlotFlexSizing(contentSlot);\n    clearEmergeDockStyles(host, dockSlot);\n\n    const isEmerge = dock.reveal === \"emerge\";\n    if (isEmerge) {\n      shell.dataset.personaDockReveal = \"emerge\";\n    } else {\n      shell.removeAttribute(\"data-persona-dock-reveal\");\n    }\n\n    const width = expanded ? dock.width : \"0px\";\n    // Detached collapse snaps padding off as width animates to 0; animate padding\n    // alongside so the canvas gutter shrinks with the column.\n    const dockTransition = dock.animate\n      ? `width 180ms ease, min-width 180ms ease, max-width 180ms ease, flex-basis 180ms ease${detached ? \", padding 180ms ease\" : \"\"}`\n      : \"none\";\n    const collapsedClosed = !expanded;\n\n    dockSlot.style.display = \"flex\";\n    dockSlot.style.flexDirection = \"column\";\n    dockSlot.style.flex = `0 0 ${width}`;\n    dockSlot.style.width = width;\n    dockSlot.style.maxWidth = width;\n    dockSlot.style.minWidth = width;\n    dockSlot.style.minHeight = \"0\";\n    applyInFlowDockSlotPosition(dockSlot, dock.maxHeight);\n    dockSlot.style.overflow =\n      isEmerge ? \"hidden\" : collapsedClosed ? \"hidden\" : \"visible\";\n    dockSlot.style.transition = dockTransition;\n\n    if (isEmerge) {\n      dockSlot.style.alignItems = dock.side === \"right\" ? \"flex-start\" : \"flex-end\";\n      // Emerge pins the host to a fixed width and reveals it as the slot grows.\n      // When detached, shrink that fixed width by the padding so the host fits\n      // the padded slot content-box instead of overflowing one side. Resolve a\n      // percentage dock.width to px first: a raw % re-resolves against the\n      // padding-reduced content box and un-pins as the slot animates open.\n      const emergeHostWidth = detached\n        ? `calc(${parseDockWidthToPx(dock.width, shell.clientWidth)}px - (2 * ${detachedInset}))`\n        : dock.width;\n      host.style.width = emergeHostWidth;\n      host.style.minWidth = emergeHostWidth;\n      host.style.maxWidth = emergeHostWidth;\n      host.style.boxSizing = \"border-box\";\n    }\n  }\n};\n\nconst createDirectLayout = (target: HTMLElement, config?: AgentWidgetConfig): WidgetHostLayout => {\n  const host = target.ownerDocument.createElement(\"div\");\n  setDirectHostStyles(host, config);\n  target.appendChild(host);\n\n  return {\n    mode: \"direct\",\n    host,\n    shell: null,\n    syncWidgetState: () => {},\n    updateConfig(nextConfig?: AgentWidgetConfig) {\n      setDirectHostStyles(host, nextConfig);\n    },\n    destroy() {\n      host.remove();\n    },\n  };\n};\n\nconst createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): WidgetHostLayout => {\n  const { ownerDocument } = target;\n  const originalParent = target.parentElement;\n\n  if (!originalParent) {\n    throw new Error(\"Docked widget target must be attached to the DOM\");\n  }\n\n  const tagName = target.tagName.toUpperCase();\n  if (tagName === \"BODY\" || tagName === \"HTML\") {\n    throw new Error('Docked widget target must be a concrete container element, not \"body\" or \"html\"');\n  }\n\n  const originalNextSibling = target.nextSibling;\n  const shell = ownerDocument.createElement(\"div\");\n  const pushTrack = ownerDocument.createElement(\"div\");\n  const contentSlot = ownerDocument.createElement(\"div\");\n  const dockSlot = ownerDocument.createElement(\"aside\");\n  const host = ownerDocument.createElement(\"div\");\n  let expanded = (config?.launcher?.enabled ?? true) ? (config?.launcher?.autoExpand ?? false) : true;\n\n  pushTrack.dataset.personaDockRole = \"push-track\";\n  contentSlot.dataset.personaDockRole = \"content\";\n  dockSlot.dataset.personaDockRole = \"panel\";\n  host.dataset.personaDockRole = \"host\";\n\n  dockSlot.appendChild(host);\n  originalParent.insertBefore(shell, target);\n  contentSlot.appendChild(target);\n\n  let resizeObserver: ResizeObserver | null = null;\n\n  const disconnectResizeObserver = (): void => {\n    resizeObserver?.disconnect();\n    resizeObserver = null;\n  };\n\n  // Resolved once per config/scheme change; the resize paths reuse this so\n  // getActiveTheme never runs per frame.\n  let detachedSlotStyles: DetachedSlotStyles | null = null;\n  const refreshDetachedSlotStyles = (): void => {\n    detachedSlotStyles =\n      config?.launcher?.detachedPanel === true ? resolveDetachedSlotStyles(config) : null;\n  };\n  refreshDetachedSlotStyles();\n\n  const reapplyDockStyles = (): void => {\n    applyDockStyles(shell, pushTrack, contentSlot, dockSlot, host, config, expanded, detachedSlotStyles);\n  };\n\n  // Scheme-dependent canvas tokens are baked to literals at layout time, so an\n  // OS dark-mode flip leaves a stale canvas until we recompute and re-apply.\n  let cleanupSchemeObserver: (() => void) | null = null;\n  const syncSchemeObserver = (): void => {\n    cleanupSchemeObserver?.();\n    cleanupSchemeObserver = null;\n    if (config?.launcher?.detachedPanel !== true || config?.colorScheme !== \"auto\") return;\n    cleanupSchemeObserver = createThemeObserver(() => {\n      refreshDetachedSlotStyles();\n      reapplyDockStyles();\n    });\n  };\n\n  const syncPushResizeObserver = (): void => {\n    disconnectResizeObserver();\n    if (resolveDockConfig(config).reveal !== \"push\") return;\n    if (typeof ResizeObserver === \"undefined\") return;\n    resizeObserver = new ResizeObserver(() => {\n      reapplyDockStyles();\n    });\n    resizeObserver.observe(shell);\n  };\n\n  let heightChainChecked = false;\n\n  const layout = (): void => {\n    reapplyDockStyles();\n    syncPushResizeObserver();\n    // Check the height chain once, the first time the panel is actually shown\n    // in a layout that depends on it (mobile fullscreen is fixed-position and\n    // doesn't: keep checking until a dependent layout comes around).\n    if (\n      expanded &&\n      !heightChainChecked &&\n      shell.dataset.personaDockMobileFullscreen !== \"true\"\n    ) {\n      heightChainChecked = true;\n      warnIfDockHeightChainUnresolved(shell, resolveDockConfig(config));\n    }\n  };\n\n  const ownerWindow = shell.ownerDocument.defaultView;\n  const onViewportResize = (): void => {\n    layout();\n  };\n  ownerWindow?.addEventListener(\"resize\", onViewportResize);\n\n  if (resolveDockConfig(config).reveal === \"push\") {\n    pushTrack.appendChild(contentSlot);\n    pushTrack.appendChild(dockSlot);\n    shell.appendChild(pushTrack);\n  } else {\n    shell.appendChild(contentSlot);\n    shell.appendChild(dockSlot);\n  }\n\n  layout();\n  syncSchemeObserver();\n\n  return {\n    mode: \"docked\",\n    host,\n    shell,\n    syncWidgetState(state) {\n      const nextExpanded = state.launcherEnabled ? state.open : true;\n      if (expanded === nextExpanded) return;\n      expanded = nextExpanded;\n      layout();\n    },\n    updateConfig(nextConfig?: AgentWidgetConfig) {\n      config = nextConfig;\n      if ((config?.launcher?.enabled ?? true) === false) {\n        expanded = true;\n      }\n      refreshDetachedSlotStyles();\n      layout();\n      syncSchemeObserver();\n    },\n    destroy() {\n      ownerWindow?.removeEventListener(\"resize\", onViewportResize);\n      cleanupSchemeObserver?.();\n      cleanupSchemeObserver = null;\n      disconnectResizeObserver();\n      if (originalParent.isConnected) {\n        if (originalNextSibling && originalNextSibling.parentNode === originalParent) {\n          originalParent.insertBefore(target, originalNextSibling);\n        } else {\n          originalParent.appendChild(target);\n        }\n      }\n      shell.remove();\n    },\n  };\n};\n\nexport const createWidgetHostLayout = (\n  target: HTMLElement,\n  config?: AgentWidgetConfig\n): WidgetHostLayout => {\n  if (isDockedMountMode(config)) {\n    return createDockedLayout(target, config);\n  }\n  return createDirectLayout(target, config);\n};\n","import { createAgentExperience, AgentWidgetController } from \"../ui\";\nimport { AgentWidgetConfig as _AgentWidgetConfig, AgentWidgetInitOptions, AgentWidgetEvent as _AgentWidgetEvent } from \"../types\";\nimport { isComposerBarMountMode, isDockedMountMode } from \"../utils/dock\";\nimport { createWidgetHostLayout } from \"./host-layout\";\n\nconst ensureTarget = (target: string | HTMLElement): HTMLElement => {\n  if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n    throw new Error(\"Chat widget can only be mounted in a browser environment\");\n  }\n\n  if (typeof target === \"string\") {\n    const element = document.querySelector<HTMLElement>(target);\n    if (!element) {\n      throw new Error(`Chat widget target \"${target}\" was not found`);\n    }\n    return element;\n  }\n\n  return target;\n};\n\n// CSS arrives via the installer's link[data-persona] or a consumer import of\n// @runtypelabs/persona/widget.css; the widget never self-locates its stylesheet.\n// A new URL(..., import.meta.url) here breaks consumer bundlers (webpack\n// resolves the literal at build time), so shadow roots only clone the head link.\nconst mountStyles = (root: ShadowRoot | HTMLElement, ownerDocument: Document) => {\n  if (!(root instanceof ShadowRoot)) {\n    return;\n  }\n\n  if (root.querySelector('link[data-persona]')) {\n    return;\n  }\n\n  const globalLink = ownerDocument.head.querySelector<HTMLLinkElement>(\n    'link[data-persona]'\n  );\n  if (!globalLink) {\n    return;\n  }\n\n  const clonedLink = globalLink.cloneNode(true) as HTMLLinkElement;\n  root.insertBefore(clonedLink, root.firstChild);\n};\n\nexport type AgentWidgetInitHandle = AgentWidgetController & { host: HTMLElement };\n\nexport const initAgentWidget = (\n  options: AgentWidgetInitOptions\n): AgentWidgetInitHandle => {\n  const target = ensureTarget(options.target);\n  const useShadow = options.useShadowDom === true;\n  const ownerDocument = target.ownerDocument;\n  let config = options.config;\n  let hostLayout = createWidgetHostLayout(target, config);\n  let controller: AgentWidgetController;\n  let stateUnsubs: Array<() => void> = [];\n\n  const createMount = (host: HTMLElement, nextConfig?: _AgentWidgetConfig): HTMLElement => {\n    const launcherEnabled = nextConfig?.launcher?.enabled ?? true;\n    const shouldFillHost = !launcherEnabled || isDockedMountMode(nextConfig);\n    const mount = ownerDocument.createElement(\"div\");\n    mount.setAttribute(\"data-persona-root\", \"true\");\n\n    if (shouldFillHost) {\n      mount.style.height = \"100%\";\n      mount.style.display = \"flex\";\n      mount.style.flexDirection = \"column\";\n      mount.style.flex = \"1\";\n      mount.style.minHeight = \"0\";\n    }\n\n    if (useShadow) {\n      const shadowRoot = host.attachShadow({ mode: \"open\" });\n      shadowRoot.appendChild(mount);\n      mountStyles(shadowRoot, ownerDocument);\n    } else {\n      host.appendChild(mount);\n      mountStyles(host, ownerDocument);\n    }\n\n    if (target.id) {\n      mount.setAttribute(\"data-persona-instance\", target.id);\n    }\n\n    return mount;\n  };\n\n  const syncHostState = () => {\n    hostLayout.syncWidgetState(controller.getState());\n  };\n\n  const bindHostState = () => {\n    stateUnsubs.forEach((unsubscribe) => unsubscribe());\n    stateUnsubs = [\n      controller.on(\"widget:opened\", syncHostState),\n      controller.on(\"widget:closed\", syncHostState),\n    ];\n    syncHostState();\n  };\n\n  const mountController = () => {\n    const mount = createMount(hostLayout.host, config);\n    controller = createAgentExperience(mount, config, {\n      debugTools: options.debugTools\n    });\n    bindHostState();\n  };\n\n  const destroyCurrentController = () => {\n    stateUnsubs.forEach((unsubscribe) => unsubscribe());\n    stateUnsubs = [];\n    controller.destroy();\n  };\n\n  mountController();\n  // Fired when the controller is mounted and its API is callable.\n  options.onChatReady?.();\n\n  const rebuildLayout = (nextConfig?: _AgentWidgetConfig) => {\n    destroyCurrentController();\n    hostLayout.destroy();\n    hostLayout = createWidgetHostLayout(target, nextConfig);\n    config = nextConfig;\n    mountController();\n  };\n\n  const handleBase = {\n    update(nextConfig: _AgentWidgetConfig) {\n      const mergedConfig = {\n        ...config,\n        ...nextConfig,\n        launcher: {\n          ...(config?.launcher ?? {}),\n          ...(nextConfig?.launcher ?? {}),\n          dock: {\n            ...(config?.launcher?.dock ?? {}),\n            ...(nextConfig?.launcher?.dock ?? {}),\n          },\n        },\n      } as _AgentWidgetConfig;\n      const previousDocked = isDockedMountMode(config);\n      const nextDocked = isDockedMountMode(mergedConfig);\n      const previousComposerBar = isComposerBarMountMode(config);\n      const nextComposerBar = isComposerBarMountMode(mergedConfig);\n\n      if (previousDocked !== nextDocked || previousComposerBar !== nextComposerBar) {\n        rebuildLayout(mergedConfig);\n        return;\n      }\n\n      config = mergedConfig;\n      hostLayout.updateConfig(config);\n      controller.update(nextConfig);\n      syncHostState();\n    },\n    destroy() {\n      destroyCurrentController();\n      hostLayout.destroy();\n      if (options.windowKey && typeof window !== \"undefined\") {\n        delete (window as any)[options.windowKey];\n      }\n    }\n  };\n\n  const handle = new Proxy(handleBase as AgentWidgetInitHandle, {\n    get(targetObject, prop, receiver) {\n      if (prop === \"host\") {\n        return hostLayout.host;\n      }\n\n      if (prop in targetObject) {\n        return Reflect.get(targetObject, prop, receiver);\n      }\n\n      const value = (controller as Record<PropertyKey, unknown>)[prop];\n      return typeof value === \"function\" ? (value as Function).bind(controller) : value;\n    }\n  }) as AgentWidgetInitHandle;\n\n  if (options.windowKey && typeof window !== 'undefined') {\n    (window as any)[options.windowKey] = handle;\n  }\n\n  return handle;\n};\n","/**\n * Enriched DOM context collection for providing richer page information to AI.\n *\n * Captures interactive elements, stable CSS selectors, ARIA roles, data attributes,\n * and visibility state: giving the LLM much better context than basic className/innerText.\n *\n * ## Modes\n *\n * - **structured** (default): collects candidates, scores them with optional {@link ParseRule}\n *   hooks, then applies `maxElements`. Rich containers (e.g. product cards) can surface\n *   before unrelated static noise.\n * - **simple**: legacy behavior: cap during traversal, interactive-first ordering, no rule\n *   scoring or {@link EnrichedPageElement.formattedSummary}.\n */\n\nexport interface EnrichedPageElement {\n  /** Stable CSS selector the LLM can use directly */\n  selector: string;\n  /** Lowercase tag name */\n  tagName: string;\n  /** Visible text content, trimmed */\n  text: string;\n  /** ARIA role or null */\n  role: string | null;\n  /** Interactivity classification */\n  interactivity: \"clickable\" | \"input\" | \"navigable\" | \"static\";\n  /** Relevant attributes: id, data-*, href, aria-label, type, value, name */\n  attributes: Record<string, string>;\n  /**\n   * When set (structured mode + matching rule), {@link formatEnrichedContext} prefers this\n   * markdown-like line instead of raw `text`.\n   */\n  formattedSummary?: string;\n}\n\n/** How DOM context is collected and formatted. */\nexport type DomContextMode = \"simple\" | \"structured\";\n\n/**\n * Options that control collection limits, visibility, and mode.\n * Prefer nesting these under {@link DomContextOptions.options}; top-level fields remain\n * supported for backward compatibility.\n */\nexport interface ParseOptionsConfig {\n  /**\n   * `structured` (default): score candidates with rules, then apply `maxElements`.\n   * `simple`: legacy traversal cap and ordering only: rules are ignored (with a warning\n   * if `rules` was passed on {@link DomContextOptions}).\n   */\n  mode?: DomContextMode;\n  /** Maximum number of elements to return. Default: 80 */\n  maxElements?: number;\n  /** CSS selector for elements to exclude (e.g. the widget host). Default: '.persona-host' */\n  excludeSelector?: string;\n  /** Maximum text length per element. Default: 200 */\n  maxTextLength?: number;\n  /** Only include visible elements. Default: true */\n  visibleOnly?: boolean;\n  /** Root element to walk. Default: document.body */\n  root?: HTMLElement;\n  /**\n   * Maximum candidates gathered before scoring (structured mode only).\n   * Default: `max(500, maxElements * 10)`.\n   */\n  maxCandidates?: number;\n}\n\nexport interface RuleScoringContext {\n  doc: Document;\n  maxTextLength: number;\n}\n\n/**\n * Extensible rule for structured DOM context: scoring, descendant suppression, and\n * optional formatted output.\n */\nexport interface ParseRule {\n  /** Stable id for debugging and tests */\n  id: string;\n  /**\n   * Score bonus when this rule applies to the element (0 when it does not).\n   * Higher scores are kept first when applying `maxElements`.\n   */\n  scoreElement(\n    el: HTMLElement,\n    enriched: EnrichedPageElement,\n    ctx: RuleScoringContext\n  ): number;\n  /**\n   * When `owner` is kept in the final set and matched this rule for formatting,\n   * return true to drop `descendant` (redundant price text, CTAs summarized on the card, etc.).\n   */\n  shouldSuppressDescendant?(\n    owner: HTMLElement,\n    descendant: HTMLElement,\n    descendantEnriched: EnrichedPageElement\n  ): boolean;\n  /**\n   * Markdown-like summary for the LLM. Only used when `scoreElement` &gt; 0 for this rule.\n   */\n  formatSummary?(\n    el: HTMLElement,\n    enriched: EnrichedPageElement,\n    ctx: RuleScoringContext\n  ): string | null;\n}\n\nexport interface DomContextOptions {\n  /** Nested parse options (mode, limits, root). Merged with legacy top-level fields. */\n  options?: ParseOptionsConfig;\n  /** Custom rules for structured mode. Default: {@link defaultParseRules} */\n  rules?: ParseRule[];\n  /** @inheritdoc ParseOptionsConfig.maxElements */\n  maxElements?: number;\n  /** @inheritdoc ParseOptionsConfig.excludeSelector */\n  excludeSelector?: string;\n  /** @inheritdoc ParseOptionsConfig.maxTextLength */\n  maxTextLength?: number;\n  /** @inheritdoc ParseOptionsConfig.visibleOnly */\n  visibleOnly?: boolean;\n  /** @inheritdoc ParseOptionsConfig.root */\n  root?: HTMLElement;\n}\n\nexport interface FormatEnrichedContextOptions {\n  /** When `simple`, ignore {@link EnrichedPageElement.formattedSummary}. Default: structured */\n  mode?: DomContextMode;\n}\n\nconst SKIP_TAGS = new Set([\n  \"script\",\n  \"style\",\n  \"noscript\",\n  \"svg\",\n  \"path\",\n  \"meta\",\n  \"link\",\n  \"br\",\n  \"hr\",\n]);\n\nconst INTERACTIVE_TAGS = new Set([\n  \"button\",\n  \"a\",\n  \"input\",\n  \"select\",\n  \"textarea\",\n  \"details\",\n  \"summary\",\n]);\n\nconst INTERACTIVE_ROLES = new Set([\n  \"button\",\n  \"link\",\n  \"menuitem\",\n  \"tab\",\n  \"option\",\n  \"switch\",\n  \"checkbox\",\n  \"radio\",\n  \"combobox\",\n  \"listbox\",\n  \"slider\",\n  \"spinbutton\",\n  \"textbox\",\n]);\n\n/** Class / id / data-* value hints for card-like containers */\nconst CARD_HINT_RE = /\\b(product|card|item|listing|result)\\b/i;\n\n/** Currency-like text in subtree */\nconst CURRENCY_RE =\n  /\\$[\\d,]+(?:\\.\\d{2})?|€[\\d,]+(?:\\.\\d{2})?|£[\\d,]+(?:\\.\\d{2})?|USD\\s*[\\d,]+(?:\\.\\d{2})?/i;\n\nconst BASE_SCORE_INTERACTIVE = 3000;\nconst BASE_SCORE_STATIC = 100;\n\nfunction hasCardHint(el: HTMLElement): boolean {\n  const cls = typeof el.className === \"string\" ? el.className : \"\";\n  if (CARD_HINT_RE.test(cls)) return true;\n  if (el.id && CARD_HINT_RE.test(el.id)) return true;\n  for (let i = 0; i < el.attributes.length; i++) {\n    const a = el.attributes[i];\n    if (a.name.startsWith(\"data-\") && CARD_HINT_RE.test(a.value)) return true;\n  }\n  return false;\n}\n\nfunction subtreeHasCurrency(el: HTMLElement): boolean {\n  return CURRENCY_RE.test((el.textContent ?? \"\").trim());\n}\n\nfunction subtreeHasNonTrivialLink(el: HTMLElement): boolean {\n  const anchors = el.querySelectorAll(\"a[href]\");\n  for (let i = 0; i < anchors.length; i++) {\n    const href = (anchors[i] as HTMLAnchorElement).getAttribute(\"href\") ?? \"\";\n    if (href && href !== \"#\" && !href.toLowerCase().startsWith(\"javascript:\"))\n      return true;\n  }\n  return false;\n}\n\nfunction subtreeHasButtonLike(el: HTMLElement): boolean {\n  return !!el.querySelector(\n    'button, [role=\"button\"], input[type=\"submit\"], input[type=\"button\"]'\n  );\n}\n\nfunction extractFirstPrice(text: string): string | null {\n  const m = text.match(CURRENCY_RE);\n  return m ? m[0] : null;\n}\n\nfunction extractTitleAndHref(el: HTMLElement): { title: string; href: string | null } {\n  const link =\n    el.querySelector(\n      \".product-title a, h1 a, h2 a, h3 a, h4 a, .title a, a[href]\"\n    ) ?? el.querySelector(\"a[href]\");\n  if (link && link.textContent?.trim()) {\n    const href = (link as HTMLAnchorElement).getAttribute(\"href\");\n    return {\n      title: link.textContent.trim(),\n      href: href && href !== \"#\" ? href : null,\n    };\n  }\n  const heading = el.querySelector(\"h1, h2, h3, h4, h5, h6\");\n  if (heading?.textContent?.trim()) {\n    return { title: heading.textContent.trim(), href: null };\n  }\n  return { title: \"\", href: null };\n}\n\nfunction extractCtaLabels(el: HTMLElement): string[] {\n  const labels: string[] = [];\n  const push = (s: string) => {\n    const t = s.trim();\n    if (t && !labels.includes(t)) labels.push(t);\n  };\n  el.querySelectorAll(\"button\").forEach((b) => push(b.textContent ?? \"\"));\n  el.querySelectorAll('[role=\"button\"]').forEach((b) => push(b.textContent ?? \"\"));\n  el.querySelectorAll('input[type=\"submit\"], input[type=\"button\"]').forEach((inp) => {\n    push((inp as HTMLInputElement).value ?? \"\");\n  });\n  return labels.slice(0, 6);\n}\n\nexport const COMMERCE_CARD_RULE_ID = \"commerce-card\";\nexport const RESULT_CARD_RULE_ID = \"result-card\";\n\nfunction commerceCardScore(el: HTMLElement): number {\n  if (!hasCardHint(el)) return 0;\n  if (!subtreeHasCurrency(el)) return 0;\n  if (!subtreeHasNonTrivialLink(el) && !subtreeHasButtonLike(el)) return 0;\n  return 5200;\n}\n\nfunction resultCardScore(el: HTMLElement): number {\n  if (!hasCardHint(el)) return 0;\n  if (subtreeHasCurrency(el)) return 0;\n  if (!subtreeHasNonTrivialLink(el)) return 0;\n  const text = (el.textContent ?? \"\").trim();\n  if (text.length < 20) return 0;\n  const hasTitle =\n    !!el.querySelector(\"h1, h2, h3, h4, h5, h6, .title\") ||\n    !!el.querySelector(\".snippet, .description, p\");\n  if (!hasTitle) return 0;\n  return 2800;\n}\n\n/** Default structured rules: commerce-style cards and generic search/result rows. */\nexport const defaultParseRules: ParseRule[] = [\n  {\n    id: COMMERCE_CARD_RULE_ID,\n    scoreElement(el) {\n      return commerceCardScore(el);\n    },\n    shouldSuppressDescendant(owner, descendant, enriched) {\n      if (descendant === owner || !owner.contains(descendant)) return false;\n      if (enriched.interactivity === \"static\") {\n        const t = enriched.text.trim();\n        if (t.length === 0) return true;\n        if (CURRENCY_RE.test(t) && t.length < 32) return true;\n        return false;\n      }\n      return true;\n    },\n    formatSummary(el, enriched) {\n      if (commerceCardScore(el) === 0) return null;\n      const { title, href } = extractTitleAndHref(el);\n      const price =\n        extractFirstPrice((el.textContent ?? \"\").trim()) ??\n        extractFirstPrice(enriched.text) ??\n        \"\";\n      const ctas = extractCtaLabels(el);\n      const head =\n        href && title\n          ? `[${title}](${href})${price ? `: ${price}` : \"\"}`\n          : title\n            ? `${title}${price ? `: ${price}` : \"\"}`\n            : price || enriched.text.trim().slice(0, 120);\n      const lines = [\n        head,\n        `selector: ${enriched.selector}`,\n        ctas.length ? `actions: ${ctas.join(\", \")}` : \"\",\n      ].filter(Boolean);\n      return lines.join(\"\\n\");\n    },\n  },\n  {\n    id: RESULT_CARD_RULE_ID,\n    scoreElement(el) {\n      return resultCardScore(el);\n    },\n    formatSummary(el, enriched) {\n      if (resultCardScore(el) === 0) return null;\n      const { title, href } = extractTitleAndHref(el);\n      const head =\n        href && title\n          ? `[${title}](${href})`\n          : title || enriched.text.trim().slice(0, 120);\n      const lines = [head, `selector: ${enriched.selector}`].filter(Boolean);\n      return lines.join(\"\\n\");\n    },\n  },\n];\n\ninterface ResolvedDomContextConfig {\n  mode: DomContextMode;\n  maxElements: number;\n  maxCandidates: number;\n  excludeSelector: string;\n  maxTextLength: number;\n  visibleOnly: boolean;\n  root: HTMLElement | undefined;\n  rules: ParseRule[];\n}\n\nfunction warnSimpleWithRules(): void {\n  if (typeof console !== \"undefined\" && typeof console.warn === \"function\") {\n    console.warn(\n      \"[persona] collectEnrichedPageContext: options.mode is \\\"simple\\\" but `rules` were provided; rules are ignored.\"\n    );\n  }\n}\n\nfunction resolveDomContextConfig(options: DomContextOptions): ResolvedDomContextConfig {\n  const nested = options.options ?? {};\n  const maxElements =\n    nested.maxElements ?? options.maxElements ?? 80;\n  const excludeSelector =\n    nested.excludeSelector ?? options.excludeSelector ?? \".persona-host\";\n  const maxTextLength =\n    nested.maxTextLength ?? options.maxTextLength ?? 200;\n  const visibleOnly =\n    nested.visibleOnly ?? options.visibleOnly ?? true;\n  const root = nested.root ?? options.root;\n  const mode: DomContextMode = nested.mode ?? \"structured\";\n  const maxCandidates =\n    nested.maxCandidates ?? Math.max(500, maxElements * 10);\n\n  let rules = options.rules ?? defaultParseRules;\n  if (mode === \"simple\" && options.rules && options.rules.length > 0) {\n    warnSimpleWithRules();\n    rules = [];\n  } else if (mode === \"simple\") {\n    rules = [];\n  }\n\n  return {\n    mode,\n    maxElements,\n    maxCandidates,\n    excludeSelector,\n    maxTextLength,\n    visibleOnly,\n    root,\n    rules,\n  };\n}\n\n/**\n * Escape a string for use in CSS selectors. Falls back to simple escaping\n * when CSS.escape is not available (e.g. in jsdom).\n */\nfunction cssEscape(str: string): string {\n  if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\n    return CSS.escape(str);\n  }\n  return str.replace(/([^\\w-])/g, \"\\\\$1\");\n}\n\nconst DATA_ATTR_PRIORITY = [\n  \"data-testid\",\n  \"data-product\",\n  \"data-action\",\n  \"data-id\",\n  \"data-name\",\n  \"data-type\",\n];\n\n/**\n * Classify an element's interactivity type.\n */\nfunction classifyInteractivity(\n  el: HTMLElement\n): EnrichedPageElement[\"interactivity\"] {\n  const tag = el.tagName.toLowerCase();\n  const role = el.getAttribute(\"role\");\n\n  if (tag === \"a\" && el.hasAttribute(\"href\")) return \"navigable\";\n  if (tag === \"input\" || tag === \"select\" || tag === \"textarea\") return \"input\";\n  if (\n    role === \"textbox\" ||\n    role === \"combobox\" ||\n    role === \"listbox\" ||\n    role === \"spinbutton\"\n  )\n    return \"input\";\n  if (tag === \"button\" || role === \"button\") return \"clickable\";\n  if (\n    INTERACTIVE_TAGS.has(tag) ||\n    (role && INTERACTIVE_ROLES.has(role)) ||\n    el.hasAttribute(\"tabindex\") ||\n    el.hasAttribute(\"onclick\") ||\n    el.getAttribute(\"contenteditable\") === \"true\"\n  )\n    return \"clickable\";\n\n  return \"static\";\n}\n\n/**\n * Check if an element is visible.\n * Uses a defensive approach: only marks as invisible when we have positive evidence\n * of hidden state (display:none, visibility:hidden, hidden attribute).\n * offsetParent is unreliable in non-layout environments (e.g. jsdom).\n */\nfunction isElementVisible(el: HTMLElement): boolean {\n  if (el.hidden) return false;\n\n  try {\n    const style = getComputedStyle(el);\n    if (style.display === \"none\") return false;\n    if (style.visibility === \"hidden\") return false;\n  } catch {\n    // getComputedStyle can fail in some environments: assume visible\n  }\n\n  if (el.style.display === \"none\") return false;\n  if (el.style.visibility === \"hidden\") return false;\n\n  return true;\n}\n\n/**\n * Collect relevant attributes from an element.\n */\nfunction collectAttributes(el: HTMLElement): Record<string, string> {\n  const attrs: Record<string, string> = {};\n\n  const id = el.id;\n  if (id) attrs[\"id\"] = id;\n\n  const href = el.getAttribute(\"href\");\n  if (href) attrs[\"href\"] = href;\n\n  const ariaLabel = el.getAttribute(\"aria-label\");\n  if (ariaLabel) attrs[\"aria-label\"] = ariaLabel;\n\n  const type = el.getAttribute(\"type\");\n  if (type) attrs[\"type\"] = type;\n\n  const value = el.getAttribute(\"value\");\n  if (value) attrs[\"value\"] = value;\n\n  const name = el.getAttribute(\"name\");\n  if (name) attrs[\"name\"] = name;\n\n  const role = el.getAttribute(\"role\");\n  if (role) attrs[\"role\"] = role;\n\n  for (let i = 0; i < el.attributes.length; i++) {\n    const attr = el.attributes[i];\n    if (attr.name.startsWith(\"data-\")) {\n      attrs[attr.name] = attr.value;\n    }\n  }\n\n  return attrs;\n}\n\n/**\n * Generate a stable, unique CSS selector for an element.\n * Priority: #id → [data-testid]/[data-product] → tag.classes with :nth-of-type()\n */\nexport function generateStableSelector(el: HTMLElement): string {\n  const tag = el.tagName.toLowerCase();\n\n  if (el.id) {\n    const sel = `#${cssEscape(el.id)}`;\n    try {\n      if (el.ownerDocument.querySelectorAll(sel).length === 1) return sel;\n    } catch {\n      // invalid selector, fall through\n    }\n  }\n\n  for (const attr of DATA_ATTR_PRIORITY) {\n    const val = el.getAttribute(attr);\n    if (val) {\n      const sel = `${tag}[${attr}=\"${cssEscape(val)}\"]`;\n      try {\n        if (el.ownerDocument.querySelectorAll(sel).length === 1) return sel;\n      } catch {\n        // invalid selector, fall through\n      }\n    }\n  }\n\n  const classes = Array.from(el.classList)\n    .filter((c) => c && !c.startsWith(\"persona-\"))\n    .slice(0, 3);\n\n  if (classes.length > 0) {\n    const classSel = `${tag}.${classes.map((c) => cssEscape(c)).join(\".\")}`;\n    try {\n      if (el.ownerDocument.querySelectorAll(classSel).length === 1) return classSel;\n    } catch {\n      // fall through\n    }\n\n    const parent = el.parentElement;\n    if (parent) {\n      const siblings = Array.from(parent.querySelectorAll(`:scope > ${tag}`));\n      const index = siblings.indexOf(el);\n      if (index >= 0) {\n        const nthSel = `${classSel}:nth-of-type(${index + 1})`;\n        try {\n          if (el.ownerDocument.querySelectorAll(nthSel).length === 1) return nthSel;\n        } catch {\n          // fall through\n        }\n      }\n    }\n  }\n\n  const parent = el.parentElement;\n  if (parent) {\n    const siblings = Array.from(parent.querySelectorAll(`:scope > ${tag}`));\n    const index = siblings.indexOf(el);\n    if (index >= 0) {\n      return `${tag}:nth-of-type(${index + 1})`;\n    }\n  }\n\n  return tag;\n}\n\nfunction baseInteractivityScore(\n  interactivity: EnrichedPageElement[\"interactivity\"]\n): number {\n  return interactivity === \"static\" ? BASE_SCORE_STATIC : BASE_SCORE_INTERACTIVE;\n}\n\ninterface ScoredCandidate {\n  el: HTMLElement;\n  domIndex: number;\n  enriched: EnrichedPageElement;\n  score: number;\n  formattingRule: ParseRule | null;\n}\n\nfunction buildEnriched(\n  el: HTMLElement,\n  maxTextLength: number\n): EnrichedPageElement {\n  const tag = el.tagName.toLowerCase();\n  const text = (el.textContent ?? \"\").trim().substring(0, maxTextLength);\n  return {\n    selector: generateStableSelector(el),\n    tagName: tag,\n    text,\n    role: el.getAttribute(\"role\"),\n    interactivity: classifyInteractivity(el),\n    attributes: collectAttributes(el),\n  };\n}\n\nfunction scoreCandidate(\n  el: HTMLElement,\n  enriched: EnrichedPageElement,\n  rules: ParseRule[],\n  ctx: RuleScoringContext\n): { score: number; formattingRule: ParseRule | null } {\n  let score = baseInteractivityScore(enriched.interactivity);\n  let formattingRule: ParseRule | null = null;\n  for (const rule of rules) {\n    const bonus = rule.scoreElement(el, enriched, ctx);\n    if (bonus > 0) {\n      score += bonus;\n      if (rule.formatSummary && !formattingRule) formattingRule = rule;\n    }\n  }\n  return { score, formattingRule };\n}\n\nfunction shouldSuppress(\n  kept: ScoredCandidate[],\n  cand: ScoredCandidate\n): boolean {\n  for (const k of kept) {\n    if (cand.el === k.el) continue;\n    if (!k.formattingRule?.shouldSuppressDescendant) continue;\n    if (!k.el.contains(cand.el)) continue;\n    if (\n      k.formattingRule.shouldSuppressDescendant(\n        k.el,\n        cand.el,\n        cand.enriched\n      )\n    )\n      return true;\n  }\n  return false;\n}\n\nfunction collectStructured(\n  cfg: ResolvedDomContextConfig,\n  rootEl: HTMLElement\n): EnrichedPageElement[] {\n  const ctx: RuleScoringContext = {\n    doc: rootEl.ownerDocument,\n    maxTextLength: cfg.maxTextLength,\n  };\n\n  const seenSelectors = new Set<string>();\n  const raw: ScoredCandidate[] = [];\n  let domIndex = 0;\n\n  const walker = document.createTreeWalker(rootEl, NodeFilter.SHOW_ELEMENT, null);\n  let node: Node | null = walker.currentNode;\n\n  while (node && raw.length < cfg.maxCandidates) {\n    if (node.nodeType === Node.ELEMENT_NODE) {\n      const el = node as HTMLElement;\n      const tag = el.tagName.toLowerCase();\n\n      if (SKIP_TAGS.has(tag)) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      if (cfg.excludeSelector) {\n        try {\n          if (el.closest(cfg.excludeSelector)) {\n            node = walker.nextNode();\n            continue;\n          }\n        } catch {\n          // invalid selector\n        }\n      }\n\n      if (cfg.visibleOnly && !isElementVisible(el)) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      const enriched = buildEnriched(el, cfg.maxTextLength);\n      const hasText = enriched.text.length > 0;\n      const hasMeaningfulAttrs =\n        Object.keys(enriched.attributes).length > 0 &&\n        !Object.keys(enriched.attributes).every((k) => k === \"role\");\n\n      if (!hasText && !hasMeaningfulAttrs) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      if (seenSelectors.has(enriched.selector)) {\n        node = walker.nextNode();\n        continue;\n      }\n      seenSelectors.add(enriched.selector);\n\n      const { score, formattingRule } = scoreCandidate(\n        el,\n        enriched,\n        cfg.rules,\n        ctx\n      );\n      raw.push({ el, domIndex, enriched, score, formattingRule });\n      domIndex += 1;\n    }\n    node = walker.nextNode();\n  }\n\n  raw.sort((a, b) => {\n    const sa = a.enriched.interactivity === \"static\" ? 1 : 0;\n    const sb = b.enriched.interactivity === \"static\" ? 1 : 0;\n    if (sa !== sb) return sa - sb;\n    if (b.score !== a.score) return b.score - a.score;\n    return a.domIndex - b.domIndex;\n  });\n\n  const kept: ScoredCandidate[] = [];\n  for (const cand of raw) {\n    if (kept.length >= cfg.maxElements) break;\n    if (shouldSuppress(kept, cand)) continue;\n    kept.push(cand);\n  }\n\n  kept.sort((a, b) => {\n    const sa = a.enriched.interactivity === \"static\" ? 1 : 0;\n    const sb = b.enriched.interactivity === \"static\" ? 1 : 0;\n    if (sa !== sb) return sa - sb;\n    if (sa === 1 && b.score !== a.score) return b.score - a.score;\n    return a.domIndex - b.domIndex;\n  });\n\n  return kept.map((c) => {\n    let formattedSummary: string | undefined;\n    if (c.formattingRule?.formatSummary) {\n      const line = c.formattingRule.formatSummary(c.el, c.enriched, ctx);\n      if (line) formattedSummary = line;\n    }\n    const out: EnrichedPageElement = { ...c.enriched };\n    if (formattedSummary) out.formattedSummary = formattedSummary;\n    return out;\n  });\n}\n\nfunction collectSimple(\n  cfg: ResolvedDomContextConfig,\n  rootEl: HTMLElement\n): EnrichedPageElement[] {\n  const elements: EnrichedPageElement[] = [];\n  const seenSelectors = new Set<string>();\n\n  const walker = document.createTreeWalker(rootEl, NodeFilter.SHOW_ELEMENT, null);\n  let node: Node | null = walker.currentNode;\n\n  while (node && elements.length < cfg.maxElements) {\n    if (node.nodeType === Node.ELEMENT_NODE) {\n      const el = node as HTMLElement;\n      const tag = el.tagName.toLowerCase();\n\n      if (SKIP_TAGS.has(tag)) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      if (cfg.excludeSelector) {\n        try {\n          if (el.closest(cfg.excludeSelector)) {\n            node = walker.nextNode();\n            continue;\n          }\n        } catch {\n          // invalid selector\n        }\n      }\n\n      if (cfg.visibleOnly && !isElementVisible(el)) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      const enriched = buildEnriched(el, cfg.maxTextLength);\n      const hasText = enriched.text.length > 0;\n      const hasMeaningfulAttrs =\n        Object.keys(enriched.attributes).length > 0 &&\n        !Object.keys(enriched.attributes).every((k) => k === \"role\");\n\n      if (!hasText && !hasMeaningfulAttrs) {\n        node = walker.nextNode();\n        continue;\n      }\n\n      if (!seenSelectors.has(enriched.selector)) {\n        seenSelectors.add(enriched.selector);\n        elements.push(enriched);\n      }\n    }\n    node = walker.nextNode();\n  }\n\n  const interactive: EnrichedPageElement[] = [];\n  const staticEls: EnrichedPageElement[] = [];\n  for (const el of elements) {\n    if (el.interactivity !== \"static\") interactive.push(el);\n    else staticEls.push(el);\n  }\n\n  return [...interactive, ...staticEls].slice(0, cfg.maxElements);\n}\n\n/**\n * Collect enriched page context from the DOM.\n *\n * - **Default (structured):** walks up to `maxCandidates` nodes, scores with\n *   {@link defaultParseRules} (or `rules`), suppresses redundant descendants when a\n *   formatting rule matches, then keeps the top `maxElements` by score (DOM order tie-break).\n * - **simple:** legacy path: stops once `maxElements` nodes are collected during traversal\n *   and sorts interactive before static.\n *\n * Pass `options: { mode: \"simple\" }` to disable rules. If `mode` is `simple` and `rules` is\n * non-empty, rules are ignored and a console warning is emitted.\n */\nexport function collectEnrichedPageContext(\n  options: DomContextOptions = {}\n): EnrichedPageElement[] {\n  const cfg = resolveDomContextConfig(options);\n  const rootEl = cfg.root ?? document.body;\n  if (!rootEl) return [];\n\n  if (cfg.mode === \"simple\") {\n    return collectSimple(cfg, rootEl);\n  }\n  return collectStructured(cfg, rootEl);\n}\n\nconst TEXT_PREVIEW_LEN = 100;\n\n/**\n * Format enriched page elements as a structured string for LLM consumption.\n * When `mode` is structured (default) and elements include {@link EnrichedPageElement.formattedSummary},\n * those render under **Structured summaries** before the usual interactivity groups.\n */\nexport function formatEnrichedContext(\n  elements: EnrichedPageElement[],\n  options: FormatEnrichedContextOptions = {}\n): string {\n  if (elements.length === 0) {\n    return \"No page elements found.\";\n  }\n\n  const mode: DomContextMode = options.mode ?? \"structured\";\n  const sections: string[] = [];\n\n  if (mode === \"structured\") {\n    const summaries = elements\n      .map((el) => el.formattedSummary)\n      .filter((s): s is string => !!s && s.length > 0);\n    if (summaries.length > 0) {\n      sections.push(\n        `Structured summaries:\\n${summaries.map((s) => `- ${s.split(\"\\n\").join(\"\\n  \")}`).join(\"\\n\")}`\n      );\n    }\n  }\n\n  const groups: Record<string, EnrichedPageElement[]> = {\n    clickable: [],\n    navigable: [],\n    input: [],\n    static: [],\n  };\n\n  for (const el of elements) {\n    if (mode === \"structured\" && el.formattedSummary) continue;\n    groups[el.interactivity].push(el);\n  }\n\n  if (groups.clickable.length > 0) {\n    const lines = groups.clickable.map(\n      (el) =>\n        `- ${el.selector}: \"${el.text.substring(0, TEXT_PREVIEW_LEN)}\" (clickable)`\n    );\n    sections.push(`Interactive elements:\\n${lines.join(\"\\n\")}`);\n  }\n\n  if (groups.navigable.length > 0) {\n    const lines = groups.navigable.map(\n      (el) =>\n        `- ${el.selector}${el.attributes.href ? `[href=\"${el.attributes.href}\"]` : \"\"}: \"${el.text.substring(0, TEXT_PREVIEW_LEN)}\" (navigable)`\n    );\n    sections.push(`Navigation links:\\n${lines.join(\"\\n\")}`);\n  }\n\n  if (groups.input.length > 0) {\n    const lines = groups.input.map(\n      (el) =>\n        `- ${el.selector}${el.attributes.type ? `[type=\"${el.attributes.type}\"]` : \"\"}: \"${el.text.substring(0, TEXT_PREVIEW_LEN)}\" (input)`\n    );\n    sections.push(`Form inputs:\\n${lines.join(\"\\n\")}`);\n  }\n\n  if (groups.static.length > 0) {\n    const lines = groups.static.map(\n      (el) => `- ${el.selector}: \"${el.text.substring(0, TEXT_PREVIEW_LEN)}\"`\n    );\n    sections.push(`Content:\\n${lines.join(\"\\n\")}`);\n  }\n\n  return sections.join(\"\\n\\n\");\n}\n","import type { PersonaTheme, PersonaThemePlugin } from '../types/theme';\n\nexport function accessibilityPlugin(): PersonaThemePlugin {\n  return {\n    name: '@persona/accessibility',\n    version: '1.0.0',\n    transform(theme: PersonaTheme): PersonaTheme {\n      return {\n        ...theme,\n        semantic: {\n          ...theme.semantic,\n          colors: {\n            ...theme.semantic.colors,\n            interactive: {\n              ...theme.semantic.colors.interactive,\n              focus: 'palette.colors.primary.700',\n              disabled: 'palette.colors.gray.300',\n            },\n          },\n        },\n      };\n    },\n    cssVariables: {\n      '--persona-accessibility-focus-ring':\n        '0 0 0 2px var(--persona-semantic-colors-surface, #fff), 0 0 0 4px var(--persona-semantic-colors-interactive-focus, #0f0f0f)',\n    },\n  };\n}\n\nexport function animationsPlugin(): PersonaThemePlugin {\n  return {\n    name: '@persona/animations',\n    version: '1.0.0',\n    transform(theme: PersonaTheme): PersonaTheme {\n      return {\n        ...theme,\n        palette: {\n          ...theme.palette,\n          transitions: {\n            fast: '150ms',\n            normal: '200ms',\n            slow: '300ms',\n            bounce: '500ms cubic-bezier(0.68, -0.55, 0.265, 1.55)',\n          },\n          easings: {\n            easeIn: 'cubic-bezier(0.4, 0, 1, 1)',\n            easeOut: 'cubic-bezier(0, 0, 0.2, 1)',\n            easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',\n          },\n        },\n      };\n    },\n    cssVariables: {\n      '--persona-transition-fast': '150ms ease',\n      '--persona-transition-normal': '200ms ease',\n      '--persona-transition-slow': '300ms ease',\n    },\n  };\n}\n\nexport function brandPlugin(brandConfig: {\n  colors?: { primary?: string; secondary?: string; accent?: string };\n  logo?: string;\n}): PersonaThemePlugin {\n  return {\n    name: '@persona/brand',\n    version: '1.0.0',\n    transform(theme: PersonaTheme): PersonaTheme {\n      const newPalette = { ...theme.palette };\n\n      if (brandConfig.colors?.primary) {\n        newPalette.colors = {\n          ...newPalette.colors,\n          primary: {\n            50: adjustColor(brandConfig.colors.primary, 0.95),\n            100: adjustColor(brandConfig.colors.primary, 0.9),\n            200: adjustColor(brandConfig.colors.primary, 0.8),\n            300: adjustColor(brandConfig.colors.primary, 0.7),\n            400: adjustColor(brandConfig.colors.primary, 0.6),\n            500: brandConfig.colors.primary,\n            600: adjustColor(brandConfig.colors.primary, 0.8),\n            700: adjustColor(brandConfig.colors.primary, 0.7),\n            800: adjustColor(brandConfig.colors.primary, 0.6),\n            900: adjustColor(brandConfig.colors.primary, 0.5),\n            950: adjustColor(brandConfig.colors.primary, 0.45),\n          },\n        };\n      }\n\n      return {\n        ...theme,\n        palette: newPalette,\n      };\n    },\n  };\n}\n\nexport function reducedMotionPlugin(): PersonaThemePlugin {\n  return {\n    name: '@persona/reduced-motion',\n    version: '1.0.0',\n    transform(theme: PersonaTheme): PersonaTheme {\n      return {\n        ...theme,\n        palette: {\n          ...theme.palette,\n          transitions: {\n            fast: '0ms',\n            normal: '0ms',\n            slow: '0ms',\n            bounce: '0ms',\n          },\n        },\n      };\n    },\n    afterResolve(resolved: Record<string, string>): Record<string, string> {\n      return {\n        ...resolved,\n        '--persona-transition-fast': '0ms',\n        '--persona-transition-normal': '0ms',\n        '--persona-transition-slow': '0ms',\n      };\n    },\n  };\n}\n\nexport function highContrastPlugin(): PersonaThemePlugin {\n  return {\n    name: '@persona/high-contrast',\n    version: '1.0.0',\n    transform(theme: PersonaTheme): PersonaTheme {\n      return {\n        ...theme,\n        semantic: {\n          ...theme.semantic,\n          colors: {\n            ...theme.semantic.colors,\n            text: 'palette.colors.gray.950',\n            textMuted: 'palette.colors.gray.700',\n            border: 'palette.colors.gray.900',\n            divider: 'palette.colors.gray.900',\n          },\n        },\n      };\n    },\n  };\n}\n\nfunction adjustColor(hex: string, factor: number): string {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n\n  const nr = Math.round(r + (255 - r) * (1 - factor));\n  const ng = Math.round(g + (255 - g) * (1 - factor));\n  const nb = Math.round(b + (255 - b) * (1 - factor));\n\n  return `#${nr.toString(16).padStart(2, '0')}${ng.toString(16).padStart(2, '0')}${nb.toString(16).padStart(2, '0')}`;\n}\n\nexport function createPlugin(config: {\n  name: string;\n  version: string;\n  transform?: (theme: PersonaTheme) => PersonaTheme;\n  cssVariables?: Record<string, string>;\n  afterResolve?: (resolved: Record<string, string>) => Record<string, string>;\n}): PersonaThemePlugin {\n  return {\n    name: config.name,\n    version: config.version,\n    transform: config.transform || ((theme) => theme),\n    cssVariables: config.cssVariables,\n    afterResolve: config.afterResolve,\n  };\n}\n","import type { AgentWidgetConfig } from \"./types\";\nimport type { DeepPartial, PersonaTheme } from \"./types/theme\";\nimport { DEFAULT_FLOATING_LAUNCHER_WIDTH } from \"./defaults\";\n\n/**\n * A named preset containing partial widget configuration.\n * Apply with: `createAgentExperience(el, { ...PRESET_SHOP.config, apiUrl: '...' })`\n * or via IIFE: `{ ...AgentWidget.PRESETS.shop.config, apiUrl: '...' }`\n */\nexport interface WidgetPreset {\n  id: string;\n  label: string;\n  config: Partial<AgentWidgetConfig>;\n}\n\n/** Shopping palette + semantic roles (matches prior shop preset visuals). */\nconst SHOP_THEME: DeepPartial<PersonaTheme> = {\n  palette: {\n    colors: {\n      primary: { 500: \"#111827\" },\n      accent: { 600: \"#1d4ed8\" },\n      gray: {\n        50: \"#ffffff\",\n        100: \"#f8fafc\",\n        200: \"#f1f5f9\",\n        500: \"#6b7280\",\n        900: \"#000000\",\n      },\n    },\n    radius: {\n      sm: \"0.75rem\",\n      md: \"1rem\",\n      lg: \"1.5rem\",\n      launcher: \"9999px\",\n      button: \"9999px\",\n    },\n  },\n  semantic: {\n    colors: {\n      primary: \"palette.colors.primary.500\",\n      textInverse: \"palette.colors.gray.50\",\n    },\n  },\n};\n\nconst PANEL_EDGELESS_THEME: DeepPartial<PersonaTheme> = {\n  components: {\n    panel: {\n      borderRadius: \"0\",\n      shadow: \"none\",\n    },\n  },\n};\n\n/**\n * Shopping / e-commerce preset.\n * Dark header, rounded launchers, shopping-oriented copy.\n */\nexport const PRESET_SHOP: WidgetPreset = {\n  id: \"shop\",\n  label: \"Shopping Assistant\",\n  config: {\n    theme: SHOP_THEME,\n    launcher: {\n      title: \"Shopping Assistant\",\n      subtitle: \"Here to help you find what you need\",\n      agentIconText: \"🛍️\",\n      position: \"bottom-right\",\n      width: DEFAULT_FLOATING_LAUNCHER_WIDTH,\n    },\n    copy: {\n      welcomeTitle: \"Welcome to our shop!\",\n      welcomeSubtitle: \"I can help you find products and answer questions\",\n      inputPlaceholder: \"Ask me anything...\",\n      sendButtonLabel: \"Send\",\n    },\n    suggestionChips: [\n      \"What can you help me with?\",\n      \"Tell me about your features\",\n      \"How does this work?\",\n    ],\n  },\n};\n\n/**\n * Minimal preset.\n * Stripped-down header, no launcher button, suitable for inline embeds.\n */\nexport const PRESET_MINIMAL: WidgetPreset = {\n  id: \"minimal\",\n  label: \"Minimal\",\n  config: {\n    launcher: {\n      enabled: false,\n      fullHeight: true,\n    },\n    layout: {\n      header: {\n        layout: \"minimal\",\n        showCloseButton: false,\n      },\n      messages: {\n        layout: \"minimal\",\n      },\n    },\n    theme: PANEL_EDGELESS_THEME,\n  },\n};\n\n/**\n * Fullscreen assistant preset.\n * No launcher, content-max-width constrained, minimal header.\n */\nexport const PRESET_FULLSCREEN: WidgetPreset = {\n  id: \"fullscreen\",\n  label: \"Fullscreen Assistant\",\n  config: {\n    launcher: {\n      enabled: false,\n      fullHeight: true,\n    },\n    layout: {\n      header: {\n        layout: \"minimal\",\n        showCloseButton: false,\n      },\n      contentMaxWidth: \"72ch\",\n    },\n    theme: PANEL_EDGELESS_THEME,\n  },\n};\n\n/** All named presets keyed by ID. */\nexport const PRESETS: Record<string, WidgetPreset> = {\n  shop: PRESET_SHOP,\n  minimal: PRESET_MINIMAL,\n  fullscreen: PRESET_FULLSCREEN,\n};\n\n/** Look up a preset by ID. */\nexport function getPreset(id: string): WidgetPreset | undefined {\n  return PRESETS[id];\n}\n","import {\n  initAgentWidget as initAgentWidgetFn,\n  type AgentWidgetInitHandle\n} from \"./runtime/init\";\n\nexport type {\n  AgentWidgetConfig,\n  TargetResolver,\n  ResolvedTarget,\n  AgentWidgetFeatureFlags,\n  AgentWidgetArtifactsFeature,\n  AgentWidgetArtifactsLayoutConfig,\n  PersonaArtifactDisplayMode,\n  PersonaArtifactKind,\n  PersonaArtifactRecord,\n  PersonaArtifactActionContext,\n  PersonaArtifactStatusLabelContext,\n  PersonaArtifactCustomAction,\n  PersonaArtifactManualUpsert,\n  ArtifactConfigPayload,\n  AgentWidgetInitOptions,\n  AgentWidgetMessage,\n  AgentWidgetLauncherConfig,\n  AgentWidgetDockConfig,\n  AgentWidgetEvent,\n  AgentWidgetStreamParser,\n  AgentWidgetStreamParserResult,\n  AgentWidgetRequestPayload,\n  // Context provider types (e.g. for config.contextProviders)\n  AgentWidgetContextProvider,\n  AgentWidgetContextProviderContext,\n  AgentWidgetCustomFetch,\n  AgentWidgetSSEEventParser,\n  AgentWidgetSSEEventResult,\n  AgentWidgetHeadersFunction,\n  // Multi-modal content types\n  TextContentPart,\n  ImageContentPart,\n  ContentPart,\n  MessageContent,\n  // Attachment config type\n  AgentWidgetAttachmentsConfig,\n  AgentWidgetComposerConfig,\n  // Layout types\n  AgentWidgetLayoutConfig,\n  AgentWidgetHeaderLayoutConfig,\n  AgentWidgetMessageLayoutConfig,\n  AgentWidgetAvatarConfig,\n  AgentWidgetTimestampConfig,\n  WidgetLayoutSlot,\n  SlotRenderer,\n  SlotRenderContext,\n  HeaderRenderContext,\n  MessageRenderContext,\n  // Markdown types\n  AgentWidgetMarkdownConfig,\n  AgentWidgetMarkdownOptions,\n  AgentWidgetMarkdownRendererOverrides,\n  // Message actions types\n  AgentWidgetMessageActionsConfig,\n  AgentWidgetMessageFeedback,\n  // Client token types\n  ClientSession,\n  ClientInitResponse,\n  ClientChatRequest,\n  ClientFeedbackRequest,\n  ClientFeedbackType,\n  // Message injection types\n  InjectMessageOptions,\n  InjectAssistantMessageOptions,\n  InjectUserMessageOptions,\n  InjectSystemMessageOptions,\n  InjectComponentDirectiveOptions,\n  // Loading indicator types\n  LoadingIndicatorRenderContext,\n  AgentWidgetLoadingIndicatorConfig,\n  // Idle indicator types\n  IdleIndicatorRenderContext,\n  // Agent execution types\n  AgentConfig,\n  AgentLoopConfig,\n  AgentToolsConfig,\n  AgentRequestOptions,\n  AgentExecutionState,\n  AgentMessageMetadata,\n  AgentWidgetAgentRequestPayload,\n  // Approval types\n  AgentWidgetApproval,\n  AgentWidgetApprovalConfig,\n  AgentWidgetApprovalDecisionOptions,\n  // WebMCP: page-discovered tool consumption\n  AgentWidgetWebMcpConfig,\n  ClientToolDefinition,\n  WebMcpConfirmHandler,\n  WebMcpConfirmInfo,\n  WebMcpToolResult,\n  // Event stream types\n  SSEEventRecord,\n  EventStreamConfig,\n  EventStreamBadgeColor,\n  EventStreamViewRenderContext,\n  EventStreamRowRenderContext,\n  EventStreamToolbarRenderContext,\n  EventStreamPayloadRenderContext,\n  // Controller event map\n  AgentWidgetControllerEventMap,\n  AgentWidgetReadAloudEvent,\n  // Ask-user-question (built-in answer-pill sheet) types\n  AskUserQuestionPayload,\n  AskUserQuestionPrompt,\n  AskUserQuestionOption,\n  AgentWidgetAskUserQuestionFeature,\n  AgentWidgetAskUserQuestionStyles\n} from \"./types\";\n\nexport type {\n  RuntypeExecutionStreamEvent,\n  RuntypeFlowSSEEvent,\n  RuntypeStreamEventOf,\n  RuntypeTurnCompleteEvent,\n  RuntypeStepCompleteEvent,\n  RuntypeStopReasonKind,\n  RuntypeClientInitRequest,\n  RuntypeClientInitResponse,\n  RuntypeClientChatRequest,\n  RuntypeClientChatStreamEvent,\n  RuntypeClientResumeRequest,\n  RuntypeClientResumeStreamEvent,\n  RuntypeClientFeedbackRequest,\n  RuntypeClientFeedbackResponse,\n  RuntypeClientFeedbackType,\n} from \"./generated/runtype-openapi-contract\";\n\nexport {\n  ASK_USER_QUESTION_TOOL_NAME,\n  createAskUserQuestionBubble,\n  ensureAskUserQuestionSheet,\n  removeAskUserQuestionSheet,\n  isAskUserQuestionMessage,\n  parseAskUserQuestionPayload\n} from \"./components/ask-user-question-bubble\";\n\nexport {\n  ASK_USER_QUESTION_CLIENT_TOOL,\n  ASK_USER_QUESTION_PARAMETERS_SCHEMA,\n  builtInClientToolsForDispatch\n} from \"./ask-user-question-tool\";\n\nexport {\n  SUGGEST_REPLIES_TOOL_NAME,\n  SUGGEST_REPLIES_CLIENT_TOOL,\n  SUGGEST_REPLIES_PARAMETERS_SCHEMA,\n  isSuggestRepliesMessage,\n  parseSuggestRepliesPayload,\n  latestAgentSuggestions\n} from \"./suggest-replies-tool\";\n\nexport { initAgentWidgetFn as initAgentWidget };\nexport {\n  createWidgetHostLayout,\n  type WidgetHostLayout,\n  type WidgetHostLayoutMode\n} from \"./runtime/host-layout\";\nexport {\n  createAgentExperience,\n  type AgentWidgetController\n} from \"./ui\";\nexport {\n  AgentWidgetSession,\n  type AgentWidgetSessionStatus\n} from \"./session\";\nexport { AgentWidgetClient } from \"./client\";\nexport type { SSEEventCallback } from \"./client\";\nexport {\n  WebMcpBridge,\n  WEBMCP_TOOL_PREFIX,\n  isWebMcpToolName,\n  stripWebMcpPrefix\n} from \"./webmcp-bridge\";\nexport { createLocalStorageAdapter } from \"./utils/storage\";\nexport {\n  createActionManager,\n  defaultActionHandlers,\n  defaultJsonActionParser\n} from \"./utils/actions\";\nexport {\n  markdownPostprocessor,\n  escapeHtml,\n  directivePostprocessor,\n  createMarkdownProcessor,\n  createMarkdownProcessorFromConfig,\n  createDirectivePostprocessor\n} from \"./postprocessors\";\nexport type { MarkdownProcessorOptions } from \"./postprocessors\";\n// Markdown parser-readiness escape hatch. On the IIFE/CDN build `marked` +\n// `DOMPurify` load lazily, so content injected right after init can render as\n// escaped plain text until the chunk resolves. Hosts can `await\n// loadMarkdownParsers()` before injecting, or `onMarkdownParsersReady(cb)` to be\n// told when they land (both resolve/no-op instantly on the npm build, where the\n// parsers are bundled eagerly).\nexport {\n  loadMarkdownParsers,\n  onMarkdownParsersReady\n} from \"./markdown-parsers-loader\";\nexport type { MarkdownParsersModule } from \"./markdown-parsers-loader\";\nexport {\n  createDefaultSanitizer,\n  resolveSanitizer\n} from \"./utils/sanitize\";\nexport type { SanitizeFunction } from \"./utils/sanitize\";\nexport {\n  createPlainTextParser,\n  createJsonStreamParser,\n  createFlexibleJsonStreamParser,\n  createRegexJsonParser,\n  createXmlParser\n} from \"./utils/formatting\";\nexport {\n  // Multi-modal content utilities\n  normalizeContent,\n  getDisplayText,\n  hasImages,\n  getImageParts,\n  createTextPart,\n  createImagePart,\n  fileToImagePart,\n  validateImageFile\n} from \"./utils/content\";\nexport {\n  collectEnrichedPageContext,\n  formatEnrichedContext,\n  generateStableSelector,\n  defaultParseRules\n} from \"./utils/dom-context\";\nexport type {\n  EnrichedPageElement,\n  DomContextOptions,\n  DomContextMode,\n  ParseOptionsConfig,\n  ParseRule,\n  RuleScoringContext,\n  FormatEnrichedContextOptions\n} from \"./utils/dom-context\";\nexport {\n  AttachmentManager,\n  type PendingAttachment,\n  type AttachmentManagerConfig\n} from \"./utils/attachment-manager\";\nexport {\n  generateMessageId,\n  generateUserMessageId,\n  generateAssistantMessageId\n} from \"./utils/message-id\";\nexport { isDockedMountMode, resolveDockConfig } from \"./utils/dock\";\n// NOTE: `generateCodeSnippet` (dev/config-tool helper) is intentionally NOT\n// re-exported here so it stays out of the IIFE/CDN bundle (index-global.ts\n// re-exports from this module). It is re-added to the npm barrel in `index.ts`.\nexport { VERSION } from \"./version\";\nexport type { AgentWidgetInitHandle };\n\n// Plugin system exports\nexport type { AgentWidgetPlugin } from \"./plugins/types\";\nexport { pluginRegistry } from \"./plugins/registry\";\n\n// Stream animation plugin API: lets consumers register custom animations\n// that match the built-in surface (typewriter, pop-bubble) and subpath\n// modules (letter-rise, word-fade, wipe, glyph-cycle).\nexport {\n  registerStreamAnimationPlugin,\n  unregisterStreamAnimationPlugin,\n  listRegisteredStreamAnimations,\n} from \"./utils/stream-animation\";\nexport type {\n  StreamAnimationPlugin,\n  StreamAnimationContext,\n  AgentWidgetStreamAnimationBuffer,\n  AgentWidgetStreamAnimationBuiltinType,\n  AgentWidgetStreamAnimationType,\n  AgentWidgetStreamAnimationFeature,\n  AgentWidgetStreamAnimationPlaceholder,\n  AgentWidgetScrollMode,\n  AgentWidgetScrollRestorePosition,\n  AgentWidgetScrollBehaviorFeature,\n  AgentWidgetScrollToBottomFeature,\n  AgentWidgetComponentRenderer,\n} from \"./types\";\n\n// Action system types: needed to type the `actionHandlers` / `actionParsers`\n// config options and to author custom handlers/parsers.\nexport type {\n  AgentWidgetActionHandler,\n  AgentWidgetActionHandlerResult,\n  AgentWidgetActionParser,\n  AgentWidgetParsedAction,\n  AgentWidgetActionContext,\n  AgentWidgetActionEventPayload,\n} from \"./types\";\n\n// Dropdown utility exports\nexport { createDropdownMenu } from \"./utils/dropdown\";\nexport type { DropdownMenuItem, CreateDropdownOptions, DropdownMenuHandle } from \"./utils/dropdown\";\n\n// Icon utility exports\nexport { renderLucideIcon } from \"./utils/icons\";\nexport type { IconName } from \"./utils/icons\";\n\n// Button utility exports\nexport { createIconButton, createLabelButton, createToggleGroup, createComboButton } from \"./utils/buttons\";\nexport type {\n  CreateIconButtonOptions,\n  CreateLabelButtonOptions,\n  CreateToggleGroupOptions,\n  ToggleGroupItem,\n  ToggleGroupHandle,\n  CreateComboButtonOptions,\n  ComboButtonHandle\n} from \"./utils/buttons\";\n\n// NOTE: `createDemoCarousel` (demo-only component) is intentionally NOT\n// re-exported here so it stays out of the IIFE/CDN bundle. It is re-added to\n// the npm barrel in `index.ts`.\n\n// Theme system exports\nexport {\n  createTheme,\n  resolveTokens,\n  themeToCssVariables,\n  applyThemeVariables,\n  getActiveTheme,\n  getColorScheme,\n  detectColorScheme,\n  createThemeObserver\n} from \"./utils/theme\";\nexport {\n  DEFAULT_PALETTE,\n  DEFAULT_SEMANTIC,\n  DEFAULT_COMPONENTS,\n  validateTheme,\n  THEME_ZONES\n} from \"./utils/tokens\";\nexport type { ThemeZone } from \"./utils/tokens\";\nexport {\n  accessibilityPlugin,\n  animationsPlugin,\n  brandPlugin,\n  reducedMotionPlugin,\n  highContrastPlugin,\n  createPlugin\n} from \"./utils/plugins\";\nexport type {\n  DeepPartial,\n  PersonaTheme,\n  PersonaThemePlugin,\n  CreateThemeOptions,\n  TokenReference,\n  ColorShade,\n  ColorPalette,\n  SpacingScale,\n  TypographyScale,\n  ShadowScale,\n  BorderScale,\n  RadiusScale,\n  SemanticColors,\n  SemanticSpacing,\n  SemanticTypography,\n  ComponentTokens,\n  ArtifactToolbarTokens,\n  ArtifactTabTokens,\n  ArtifactPaneTokens,\n  IconButtonTokens,\n  LabelButtonTokens,\n  ToggleGroupTokens,\n  ThemeValidationResult,\n  ThemeValidationError\n} from \"./types/theme\";\n\n// Component system exports\nexport { componentRegistry } from \"./components/registry\";\nexport type {\n  ComponentRenderer,\n  ComponentContext,\n  ComponentRegistrationOptions\n} from \"./components/registry\";\nexport {\n  createComponentStreamParser,\n  isComponentDirectiveType\n} from \"./utils/component-parser\";\nexport type { ComponentDirective } from \"./utils/component-parser\";\nexport {\n  renderComponentDirective,\n  createComponentMiddleware,\n  hasComponentDirective,\n  extractComponentDirectiveFromMessage\n} from \"./utils/component-middleware\";\n\n// Default configuration exports\nexport {\n  DEFAULT_WIDGET_CONFIG,\n  DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,\n  DEFAULT_FLOATING_LAUNCHER_WIDTH,\n  mergeWithDefaults\n} from \"./defaults\";\nexport {\n  PRESETS,\n  getPreset,\n  PRESET_SHOP,\n  PRESET_MINIMAL,\n  PRESET_FULLSCREEN\n} from \"./presets\";\nexport type { WidgetPreset } from \"./presets\";\n\n// Layout system exports\nexport {\n  buildHeader,\n  buildComposer,\n  attachHeaderToContainer\n} from \"./components/panel\";\nexport type {\n  HeaderElements,\n  HeaderBuildContext,\n  ComposerElements,\n  ComposerBuildContext\n} from \"./components/panel\";\nexport {\n  headerLayouts,\n  getHeaderLayout,\n  buildHeaderWithLayout,\n  buildDefaultHeader,\n  buildMinimalHeader\n} from \"./components/header-layouts\";\nexport type {\n  HeaderLayoutContext,\n  HeaderLayoutRenderer\n} from \"./components/header-layouts\";\nexport {\n  createStandardBubble,\n  createBubbleWithLayout,\n  createTypingIndicator,\n  createMessageActions,\n  renderLoadingIndicatorWithFallback\n} from \"./components/message-bubble\";\nexport type {\n  MessageTransform,\n  MessageActionCallbacks,\n  LoadingIndicatorRenderer,\n  CreateStandardBubbleOptions\n} from \"./components/message-bubble\";\nexport {\n  createCSATFeedback,\n  createNPSFeedback\n} from \"./components/feedback\";\nexport type { CSATFeedbackOptions, NPSFeedbackOptions } from \"./components/feedback\";\n\n// Voice module exports\nexport {\n  createVoiceProvider,\n  createBestAvailableVoiceProvider,\n  isVoiceSupported\n} from \"./voice\";\nexport type {\n  VoiceProvider,\n  VoiceResult,\n  VoiceStatus,\n  VoiceConfig,\n  VoiceMetrics,\n  VoicePlaybackEngine,\n  PcmStreamPlayer,\n  AgentWidgetVoiceStatusEvent,\n  ReadAloudState,\n  SpeechEngine,\n  SpeechRequest,\n  SpeechCallbacks\n} from \"./types\";\n\nexport {\n  BrowserSpeechEngine,\n  pickBestVoice,\n  ReadAloudController\n} from \"./voice\";\nexport type {\n  BrowserSpeechEngineOptions,\n  ReadAloudListener,\n  FallbackSpeechEngineOptions\n} from \"./voice\";\n// RuntypeSpeechEngine + FallbackSpeechEngine ship from the\n// `@runtypelabs/persona/voice-worklet-player` subpath, not here: keeping the\n// hosted read-aloud engine out of the main bundle lets the IIFE/CDN build defer\n// it to the `runtype-tts.js` chunk (loaded only for `provider: 'runtype'`). The\n// blessed way to use it is `textToSpeech: { provider: 'runtype' }` — no import\n// needed.\n\nexport default initAgentWidgetFn;\n","import type { AgentWidgetConfig } from \"../types\";\nimport { VERSION } from \"../version\";\n\ntype ParserType = \"plain\" | \"json\" | \"regex-json\" | \"xml\";\nexport type CodeFormat = \"esm\" | \"script-installer\" | \"script-manual\" | \"script-advanced\" | \"react-component\" | \"react-advanced\";\n\n/**\n * Hook code templates for code generation.\n * Each hook can be provided as a string (code template) OR as an actual function.\n * Functions are automatically serialized via `.toString()`.\n *\n * IMPORTANT: When providing functions:\n * - Functions must be self-contained (no external variables/closures)\n * - External variables will be undefined when the generated code runs\n * - Use arrow functions or regular function expressions\n *\n * @example\n * ```typescript\n * // Both of these work:\n *\n * // As string:\n * { getHeaders: \"async () => ({ 'Authorization': 'Bearer token' })\" }\n *\n * // As function (recommended - better IDE support):\n * { getHeaders: async () => ({ 'Authorization': 'Bearer token' }) }\n * ```\n */\nexport type CodeGeneratorHooks = {\n  /**\n   * Custom getHeaders function.\n   * Should return an object with header key-value pairs.\n   *\n   * @example\n   * ```typescript\n   * async () => ({ 'Authorization': `Bearer ${await getAuthToken()}` })\n   * ```\n   */\n  getHeaders?: string | (() => Record<string, string> | Promise<Record<string, string>>);\n\n  /**\n   * Custom onFeedback callback for message actions.\n   * Receives a feedback object with type, messageId, and message.\n   *\n   * @example\n   * ```typescript\n   * (feedback) => { console.log('Feedback:', feedback.type); }\n   * ```\n   */\n  onFeedback?: string | ((feedback: { type: string; messageId: string; message: unknown }) => void);\n\n  /**\n   * Custom onCopy callback for message actions.\n   * Receives the message that was copied.\n   *\n   * @example\n   * ```typescript\n   * (message) => { analytics.track('message_copied', { id: message.id }); }\n   * ```\n   */\n  onCopy?: string | ((message: unknown) => void);\n\n  /**\n   * Custom requestMiddleware function.\n   * Receives { payload, config } context.\n   *\n   * @example\n   * ```typescript\n   * ({ payload }) => ({ ...payload, metadata: { pageUrl: window.location.href } })\n   * ```\n   */\n  requestMiddleware?: string | ((context: { payload: unknown; config: unknown }) => unknown);\n\n  /**\n   * Custom action handlers array.\n   * Array of handler functions.\n   *\n   * @example\n   * ```typescript\n   * [\n   *   (action, context) => {\n   *     if (action.type === 'custom') {\n   *       return { handled: true };\n   *     }\n   *   }\n   * ]\n   * ```\n   */\n  actionHandlers?: string | Array<(action: unknown, context: unknown) => unknown>;\n\n  /**\n   * Custom action parsers array.\n   * Array of parser functions.\n   */\n  actionParsers?: string | Array<(context: unknown) => unknown>;\n\n  /**\n   * Custom postprocessMessage function.\n   * Receives { text, message, streaming, raw } context.\n   * Will override the default markdownPostprocessor.\n   *\n   * @example\n   * ```typescript\n   * ({ text }) => customMarkdownProcessor(text)\n   * ```\n   */\n  postprocessMessage?: string | ((context: { text: string; message?: unknown; streaming?: boolean; raw?: string }) => string);\n\n  /**\n   * Custom context providers array.\n   * Array of provider functions.\n   */\n  contextProviders?: string | Array<() => unknown>;\n\n  /**\n   * Custom stream parser factory.\n   * Should be a function that returns a StreamParser.\n   */\n  streamParser?: string | (() => unknown);\n};\n\n/**\n * Options for code generation beyond format selection.\n */\nexport type CodeGeneratorOptions = {\n  /**\n   * Custom hook code to inject into the generated snippet.\n   * Hooks are JavaScript/TypeScript code strings that will be\n   * inserted at appropriate locations in the output.\n   */\n  hooks?: CodeGeneratorHooks;\n\n  /**\n   * Whether to include comments explaining each hook.\n   * @default true\n   */\n  includeHookComments?: boolean;\n\n  /**\n   * If provided, emits `windowKey` in the generated `initAgentWidget()` call\n   * so the widget handle is stored on `window[windowKey]`.\n   * Only affects script formats (script-installer, script-manual, script-advanced).\n   */\n  windowKey?: string;\n\n  /**\n   * CSS selector for the mount target. When omitted, the widget mounts on\n   * `body` (the existing default). When provided, the generated snippet mounts\n   * into that element: ESM / React / manual / advanced formats emit it as the\n   * `target` argument to `initAgentWidget()`, and `script-installer` serializes\n   * it into `data-config` (the installer reads `config.target`).\n   * @default \"body\"\n   */\n  target?: string;\n};\n\n// Internal type for normalized hooks (always strings)\ntype NormalizedHooks = {\n  [K in keyof CodeGeneratorHooks]: string | undefined;\n};\n\n/**\n * Serialize a hook value (string, function, or array of functions) to a string.\n */\nfunction serializeHook(hook: string | Function | Function[] | undefined): string | undefined {\n  if (hook === undefined) return undefined;\n  if (typeof hook === 'string') return hook;\n  if (Array.isArray(hook)) {\n    return `[${hook.map(fn => fn.toString()).join(', ')}]`;\n  }\n  return hook.toString();\n}\n\n/**\n * Normalize hooks by converting any functions to their string representations.\n */\nfunction normalizeHooks(hooks: CodeGeneratorHooks | undefined): NormalizedHooks | undefined {\n  if (!hooks) return undefined;\n\n  return {\n    getHeaders: serializeHook(hooks.getHeaders),\n    onFeedback: serializeHook(hooks.onFeedback),\n    onCopy: serializeHook(hooks.onCopy),\n    requestMiddleware: serializeHook(hooks.requestMiddleware),\n    actionHandlers: serializeHook(hooks.actionHandlers),\n    actionParsers: serializeHook(hooks.actionParsers),\n    postprocessMessage: serializeHook(hooks.postprocessMessage),\n    contextProviders: serializeHook(hooks.contextProviders),\n    streamParser: serializeHook(hooks.streamParser),\n  };\n}\n\n// =============================================================================\n// Template Literals for Code Generation\n// These are injected into generated code as-is.\n// =============================================================================\n\n/**\n * Template: Parser for JSON wrapped in markdown code fences (TypeScript).\n * @internal\n */\nconst TEMPLATE_MARKDOWN_JSON_PARSER_TS = `({ text, message }: any) => {\n  const jsonSource = (message as any).rawContent || text || message.content;\n  if (!jsonSource || typeof jsonSource !== 'string') return null;\n  let cleanJson = jsonSource\n    .replace(/^\\`\\`\\`(?:json)?\\\\s*\\\\n?/, '')\n    .replace(/\\\\n?\\`\\`\\`\\\\s*$/, '')\n    .trim();\n  if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;\n  try {\n    const parsed = JSON.parse(cleanJson);\n    if (parsed.action) return { type: parsed.action, payload: parsed };\n  } catch (e) { return null; }\n  return null;\n}`;\n\n/**\n * Template: Parser for JSON wrapped in markdown code fences (ES5).\n * @internal\n */\nconst TEMPLATE_MARKDOWN_JSON_PARSER_ES5 = `function(ctx) {\n  var jsonSource = ctx.message.rawContent || ctx.text || ctx.message.content;\n  if (!jsonSource || typeof jsonSource !== 'string') return null;\n  var cleanJson = jsonSource\n    .replace(/^\\`\\`\\`(?:json)?\\\\s*\\\\n?/, '')\n    .replace(/\\\\n?\\`\\`\\`\\\\s*$/, '')\n    .trim();\n  if (!cleanJson.startsWith('{') || !cleanJson.endsWith('}')) return null;\n  try {\n    var parsed = JSON.parse(cleanJson);\n    if (parsed.action) return { type: parsed.action, payload: parsed };\n  } catch (e) { return null; }\n  return null;\n}`;\n\n/**\n * Template: Handler for nav_then_click actions (TypeScript).\n * @internal\n */\nconst TEMPLATE_NAV_THEN_CLICK_HANDLER_TS = `(action: any, context: any) => {\n  if (action.type !== 'nav_then_click') return;\n  const payload = action.payload || action.raw || {};\n  const url = payload?.page;\n  const text = payload?.on_load_text || 'Navigating...';\n  if (!url) return { handled: true, displayText: text };\n  const messageId = context.message?.id;\n  const processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');\n  const actionKey = \\`nav_\\${messageId}_\\${url}\\`;\n  if (processedActions.includes(actionKey)) {\n    return { handled: true, displayText: text };\n  }\n  processedActions.push(actionKey);\n  localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));\n  const targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();\n  window.location.href = targetUrl;\n  return { handled: true, displayText: text };\n}`;\n\n/**\n * Template: Handler for nav_then_click actions (ES5).\n * @internal\n */\nconst TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5 = `function(action, context) {\n  if (action.type !== 'nav_then_click') return;\n  var payload = action.payload || action.raw || {};\n  var url = payload.page;\n  var text = payload.on_load_text || 'Navigating...';\n  if (!url) return { handled: true, displayText: text };\n  var messageId = context.message ? context.message.id : null;\n  var processedActions = JSON.parse(localStorage.getItem(PROCESSED_ACTIONS_KEY) || '[]');\n  var actionKey = 'nav_' + messageId + '_' + url;\n  if (processedActions.includes(actionKey)) {\n    return { handled: true, displayText: text };\n  }\n  processedActions.push(actionKey);\n  localStorage.setItem(PROCESSED_ACTIONS_KEY, JSON.stringify(processedActions));\n  var targetUrl = url.startsWith('http') ? url : new URL(url, window.location.origin).toString();\n  window.location.href = targetUrl;\n  return { handled: true, displayText: text };\n}`;\n\n/**\n * Template: Stream parser callback (TypeScript).\n * @internal\n */\nconst TEMPLATE_STREAM_PARSER_CALLBACK_TS = `(parsed: any) => {\n  if (!parsed || typeof parsed !== 'object') return null;\n  if (parsed.action === 'nav_then_click') return 'Navigating...';\n  if (parsed.action === 'message') return parsed.text || '';\n  if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';\n  return parsed.text || null;\n}`;\n\n/**\n * Template: Stream parser callback (ES5).\n * @internal\n */\nconst TEMPLATE_STREAM_PARSER_CALLBACK_ES5 = `function(parsed) {\n  if (!parsed || typeof parsed !== 'object') return null;\n  if (parsed.action === 'nav_then_click') return 'Navigating...';\n  if (parsed.action === 'message') return parsed.text || '';\n  if (parsed.action === 'message_and_click') return parsed.text || 'Processing...';\n  return parsed.text || null;\n}`;\n\nfunction detectParserTypeFromStreamParser(streamParser: any): ParserType | null {\n  if (!streamParser) return null;\n  const fnString = streamParser.toString();\n  if (fnString.includes(\"createJsonStreamParser\") || fnString.includes(\"partial-json\")) {\n    return \"json\";\n  }\n  if (fnString.includes(\"createRegexJsonParser\") || fnString.includes(\"regex\")) {\n    return \"regex-json\";\n  }\n  if (fnString.includes(\"createXmlParser\") || fnString.includes(\"<text>\")) {\n    return \"xml\";\n  }\n  return null;\n}\n\nfunction getParserTypeFromConfig(config: AgentWidgetConfig): ParserType {\n  return config.parserType ?? detectParserTypeFromStreamParser(config.streamParser) ?? \"plain\";\n}\n\n// Helper to generate toolCall config\nfunction generateToolCallConfig(config: any, indent: string): string[] {\n  const lines: string[] = [];\n  if (config.toolCall) {\n    lines.push(`${indent}toolCall: {`);\n    Object.entries(config.toolCall).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`${indent}  ${key}: \"${value}\",`);\n      }\n    });\n    lines.push(`${indent}},`);\n  }\n  return lines;\n}\n\n// Helper to generate messageActions config (with optional hook callbacks)\nfunction generateMessageActionsConfig(config: any, indent: string, hooks?: CodeGeneratorHooks): string[] {\n  const lines: string[] = [];\n  const hasSerializableProps = config.messageActions && Object.entries(config.messageActions).some(\n    ([key, value]) => key !== \"onFeedback\" && key !== \"onCopy\" && value !== undefined\n  );\n  const hasHookCallbacks = hooks?.onFeedback || hooks?.onCopy;\n\n  if (hasSerializableProps || hasHookCallbacks) {\n    lines.push(`${indent}messageActions: {`);\n\n    // Add serializable properties from config\n    if (config.messageActions) {\n      Object.entries(config.messageActions).forEach(([key, value]) => {\n        // Skip function callbacks - we'll add from hooks if provided\n        if (key === \"onFeedback\" || key === \"onCopy\") return;\n        if (typeof value === \"string\") {\n          lines.push(`${indent}  ${key}: \"${value}\",`);\n        } else if (typeof value === \"boolean\") {\n          lines.push(`${indent}  ${key}: ${value},`);\n        }\n      });\n    }\n\n    // Add hook callbacks\n    if (hooks?.onFeedback) {\n      lines.push(`${indent}  onFeedback: ${hooks.onFeedback},`);\n    }\n    if (hooks?.onCopy) {\n      lines.push(`${indent}  onCopy: ${hooks.onCopy},`);\n    }\n\n    lines.push(`${indent}},`);\n  }\n  return lines;\n}\n\n// Helper to generate markdown config (excluding renderer functions)\nfunction generateMarkdownConfig(config: any, indent: string): string[] {\n  const lines: string[] = [];\n  if (config.markdown) {\n    const hasOptions = config.markdown.options && Object.keys(config.markdown.options).length > 0;\n    const hasDisableDefaultStyles = config.markdown.disableDefaultStyles !== undefined;\n    \n    if (hasOptions || hasDisableDefaultStyles) {\n      lines.push(`${indent}markdown: {`);\n      \n      if (hasOptions) {\n        lines.push(`${indent}  options: {`);\n        Object.entries(config.markdown.options).forEach(([key, value]) => {\n          if (typeof value === \"string\") {\n            lines.push(`${indent}    ${key}: \"${value}\",`);\n          } else if (typeof value === \"boolean\") {\n            lines.push(`${indent}    ${key}: ${value},`);\n          }\n        });\n        lines.push(`${indent}  },`);\n      }\n      \n      if (hasDisableDefaultStyles) {\n        lines.push(`${indent}  disableDefaultStyles: ${config.markdown.disableDefaultStyles},`);\n      }\n      \n      lines.push(`${indent}},`);\n    }\n  }\n  return lines;\n}\n\n// Helper to generate layout config (excluding render functions and slots)\nfunction generateLayoutConfig(config: any, indent: string): string[] {\n  const lines: string[] = [];\n  if (config.layout) {\n    const hasHeader = config.layout.header && Object.keys(config.layout.header).some(\n      (key: string) => key !== \"render\"\n    );\n    const hasMessages = config.layout.messages && Object.keys(config.layout.messages).some(\n      (key: string) => key !== \"renderUserMessage\" && key !== \"renderAssistantMessage\"\n    );\n    \n    if (hasHeader || hasMessages) {\n      lines.push(`${indent}layout: {`);\n      \n      // Header config (excluding render function)\n      if (hasHeader) {\n        lines.push(`${indent}  header: {`);\n        Object.entries(config.layout.header).forEach(([key, value]) => {\n          if (key === \"render\") return; // Skip render function\n          if (typeof value === \"string\") {\n            lines.push(`${indent}    ${key}: \"${value}\",`);\n          } else if (typeof value === \"boolean\") {\n            lines.push(`${indent}    ${key}: ${value},`);\n          }\n        });\n        lines.push(`${indent}  },`);\n      }\n      \n      // Messages config (excluding render functions)\n      if (hasMessages) {\n        lines.push(`${indent}  messages: {`);\n        Object.entries(config.layout.messages).forEach(([key, value]) => {\n          // Skip render functions\n          if (key === \"renderUserMessage\" || key === \"renderAssistantMessage\") return;\n          \n          if (key === \"avatar\" && typeof value === \"object\" && value !== null) {\n            lines.push(`${indent}    avatar: {`);\n            Object.entries(value as Record<string, unknown>).forEach(([avatarKey, avatarValue]) => {\n              if (typeof avatarValue === \"string\") {\n                lines.push(`${indent}      ${avatarKey}: \"${avatarValue}\",`);\n              } else if (typeof avatarValue === \"boolean\") {\n                lines.push(`${indent}      ${avatarKey}: ${avatarValue},`);\n              }\n            });\n            lines.push(`${indent}    },`);\n          } else if (key === \"timestamp\" && typeof value === \"object\" && value !== null) {\n            // Only emit serializable timestamp properties (skip format function)\n            const hasSerializableTimestamp = Object.entries(value as Record<string, unknown>).some(\n              ([k]) => k !== \"format\"\n            );\n            if (hasSerializableTimestamp) {\n              lines.push(`${indent}    timestamp: {`);\n              Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {\n                if (tsKey === \"format\") return; // Skip format function\n                if (typeof tsValue === \"string\") {\n                  lines.push(`${indent}      ${tsKey}: \"${tsValue}\",`);\n                } else if (typeof tsValue === \"boolean\") {\n                  lines.push(`${indent}      ${tsKey}: ${tsValue},`);\n                }\n              });\n              lines.push(`${indent}    },`);\n            }\n          } else if (typeof value === \"string\") {\n            lines.push(`${indent}    ${key}: \"${value}\",`);\n          } else if (typeof value === \"boolean\") {\n            lines.push(`${indent}    ${key}: ${value},`);\n          }\n        });\n        lines.push(`${indent}  },`);\n      }\n      \n      lines.push(`${indent}},`);\n    }\n  }\n  return lines;\n}\n\n// Helper to generate hook-related config lines\nfunction generateHooksConfig(hooks: CodeGeneratorHooks | undefined, indent: string): string[] {\n  const lines: string[] = [];\n  if (!hooks) return lines;\n\n  if (hooks.getHeaders) {\n    lines.push(`${indent}getHeaders: ${hooks.getHeaders},`);\n  }\n\n  if (hooks.requestMiddleware) {\n    lines.push(`${indent}requestMiddleware: ${hooks.requestMiddleware},`);\n  }\n\n  if (hooks.actionParsers) {\n    lines.push(`${indent}actionParsers: ${hooks.actionParsers},`);\n  }\n\n  if (hooks.actionHandlers) {\n    lines.push(`${indent}actionHandlers: ${hooks.actionHandlers},`);\n  }\n\n  if (hooks.contextProviders) {\n    lines.push(`${indent}contextProviders: ${hooks.contextProviders},`);\n  }\n\n  if (hooks.streamParser) {\n    lines.push(`${indent}streamParser: ${hooks.streamParser},`);\n  }\n\n  return lines;\n}\n\nfunction appendSerializableObjectEntries(\n  lines: string[],\n  value: Record<string, unknown>,\n  indent: string\n): void {\n  Object.entries(value).forEach(([key, entryValue]) => {\n    if (entryValue === undefined || typeof entryValue === \"function\") return;\n\n    if (Array.isArray(entryValue)) {\n      lines.push(`${indent}${key}: ${JSON.stringify(entryValue)},`);\n      return;\n    }\n\n    if (entryValue && typeof entryValue === \"object\") {\n      lines.push(`${indent}${key}: {`);\n      appendSerializableObjectEntries(lines, entryValue as Record<string, unknown>, `${indent}  `);\n      lines.push(`${indent}},`);\n      return;\n    }\n\n    lines.push(`${indent}${key}: ${JSON.stringify(entryValue)},`);\n  });\n}\n\nfunction appendSerializableObjectBlock(\n  lines: string[],\n  key: string,\n  value: Record<string, unknown> | undefined,\n  indent: string\n): void {\n  if (!value) return;\n  lines.push(`${indent}${key}: {`);\n  appendSerializableObjectEntries(lines, value, `${indent}  `);\n  lines.push(`${indent}},`);\n}\n\n/**\n * Resolve the mount-target selector for a single-quoted JS string context.\n * Defaults to `body` and escapes backslashes / single quotes so an arbitrary\n * selector can't break out of the emitted `target: '...'` literal.\n */\nfunction emitTargetSelector(options?: CodeGeneratorOptions): string {\n  return (options?.target ?? \"body\").replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n}\n\nexport function generateCodeSnippet(\n  config: any,\n  format: CodeFormat = \"esm\",\n  options?: CodeGeneratorOptions\n): string {\n  // Remove non-serializable properties\n  const cleanConfig = { ...config };\n  delete cleanConfig.postprocessMessage;\n  delete cleanConfig.initialMessages;\n\n  // Normalize hooks - convert functions to strings via .toString()\n  const normalizedOptions: CodeGeneratorOptions | undefined = options\n    ? { ...options, hooks: normalizeHooks(options.hooks) as CodeGeneratorHooks }\n    : undefined;\n\n  if (format === \"esm\") {\n    return generateESMCode(cleanConfig, normalizedOptions);\n  } else if (format === \"script-installer\") {\n    return generateScriptInstallerCode(cleanConfig, normalizedOptions);\n  } else if (format === \"script-advanced\") {\n    return generateScriptAdvancedCode(cleanConfig, normalizedOptions);\n  } else if (format === \"react-component\") {\n    return generateReactComponentCode(cleanConfig, normalizedOptions);\n  } else if (format === \"react-advanced\") {\n    return generateReactAdvancedCode(cleanConfig, normalizedOptions);\n  } else {\n    return generateScriptManualCode(cleanConfig, normalizedOptions);\n  }\n}\n\nfunction generateESMCode(config: any, options?: CodeGeneratorOptions): string {\n  const hooks = options?.hooks;\n  const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);\n  const shouldEmitParserType = parserType !== \"plain\";\n\n  const lines: string[] = [\n    \"import '@runtypelabs/persona/widget.css';\",\n    \"import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';\",\n    \"\",\n    \"initAgentWidget({\",\n    `  target: '${emitTargetSelector(options)}',`,\n    \"  config: {\"\n  ];\n\n  if (config.apiUrl) lines.push(`    apiUrl: \"${config.apiUrl}\",`);\n  if (config.clientToken) lines.push(`    clientToken: \"${config.clientToken}\",`);\n  if (config.agentId) lines.push(`    agentId: \"${config.agentId}\",`);\n  if (config.target) lines.push(`    target: \"${config.target}\",`);\n  if (config.flowId) lines.push(`    flowId: \"${config.flowId}\",`);\n  if (shouldEmitParserType) lines.push(`    parserType: \"${parserType}\",`);\n\n  if (config.theme && typeof config.theme === \"object\" && Object.keys(config.theme).length > 0) {\n    appendSerializableObjectBlock(lines, \"theme\", config.theme as Record<string, unknown>, \"    \");\n  }\n\n  if (config.launcher) {\n    appendSerializableObjectBlock(lines, \"launcher\", config.launcher, \"    \");\n  }\n\n  if (config.copy) {\n    lines.push(\"    copy: {\");\n    Object.entries(config.copy).forEach(([key, value]) => {\n      lines.push(`      ${key}: \"${value}\",`);\n    });\n    lines.push(\"    },\");\n  }\n\n  if (config.sendButton) {\n    lines.push(\"    sendButton: {\");\n    Object.entries(config.sendButton).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`      ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`      ${key}: ${value},`);\n      }\n    });\n    lines.push(\"    },\");\n  }\n\n  if (config.voiceRecognition) {\n    lines.push(\"    voiceRecognition: {\");\n    Object.entries(config.voiceRecognition).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`      ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`      ${key}: ${value},`);\n      } else if (typeof value === \"number\") {\n        lines.push(`      ${key}: ${value},`);\n      }\n    });\n    lines.push(\"    },\");\n  }\n\n  if (config.statusIndicator) {\n    lines.push(\"    statusIndicator: {\");\n    Object.entries(config.statusIndicator).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`      ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`      ${key}: ${value},`);\n      }\n    });\n    lines.push(\"    },\");\n  }\n\n  if (config.features) {\n    lines.push(\"    features: {\");\n    Object.entries(config.features).forEach(([key, value]) => {\n      lines.push(`      ${key}: ${value},`);\n    });\n    lines.push(\"    },\");\n  }\n\n  if (config.suggestionChips && config.suggestionChips.length > 0) {\n    lines.push(\"    suggestionChips: [\");\n    config.suggestionChips.forEach((chip: string) => {\n      lines.push(`      \"${chip}\",`);\n    });\n    lines.push(\"    ],\");\n  }\n\n  if (config.suggestionChipsConfig) {\n    lines.push(\"    suggestionChipsConfig: {\");\n    if (config.suggestionChipsConfig.fontFamily) {\n      lines.push(`      fontFamily: \"${config.suggestionChipsConfig.fontFamily}\",`);\n    }\n    if (config.suggestionChipsConfig.fontWeight) {\n      lines.push(`      fontWeight: \"${config.suggestionChipsConfig.fontWeight}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingX) {\n      lines.push(`      paddingX: \"${config.suggestionChipsConfig.paddingX}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingY) {\n      lines.push(`      paddingY: \"${config.suggestionChipsConfig.paddingY}\",`);\n    }\n    lines.push(\"    },\");\n  }\n\n  // Add toolCall config\n  lines.push(...generateToolCallConfig(config, \"    \"));\n\n  // Add messageActions config (with hook callbacks if provided)\n  lines.push(...generateMessageActionsConfig(config, \"    \", hooks));\n\n  // Add markdown config\n  lines.push(...generateMarkdownConfig(config, \"    \"));\n\n  // Add layout config\n  lines.push(...generateLayoutConfig(config, \"    \"));\n\n  // Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)\n  lines.push(...generateHooksConfig(hooks, \"    \"));\n\n  if (config.debug) {\n    lines.push(`    debug: ${config.debug},`);\n  }\n\n  // Use custom postprocessMessage if provided, otherwise default\n  if (hooks?.postprocessMessage) {\n    lines.push(`    postprocessMessage: ${hooks.postprocessMessage}`);\n  } else {\n    lines.push(\"    postprocessMessage: ({ text }) => markdownPostprocessor(text)\");\n  }\n  lines.push(\"  }\");\n  lines.push(\"});\");\n\n  return lines.join(\"\\n\");\n}\n\nfunction generateReactComponentCode(config: any, options?: CodeGeneratorOptions): string {\n  const hooks = options?.hooks;\n  const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);\n  const shouldEmitParserType = parserType !== \"plain\";\n\n  const lines: string[] = [\n    \"// ChatWidget.tsx\",\n    \"'use client'; // Required for Next.js - remove for Vite/CRA\",\n    \"\",\n    \"import { useEffect } from 'react';\",\n    \"import '@runtypelabs/persona/widget.css';\",\n    \"import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';\",\n    \"import type { AgentWidgetInitHandle } from '@runtypelabs/persona';\",\n    \"\",\n    \"export function ChatWidget() {\",\n    \"  useEffect(() => {\",\n    \"    let handle: AgentWidgetInitHandle | null = null;\",\n    \"\",\n    \"    handle = initAgentWidget({\",\n    `      target: '${emitTargetSelector(options)}',`,\n    \"      config: {\"\n  ];\n\n  if (config.apiUrl) lines.push(`        apiUrl: \"${config.apiUrl}\",`);\n  if (config.clientToken) lines.push(`        clientToken: \"${config.clientToken}\",`);\n  if (config.agentId) lines.push(`        agentId: \"${config.agentId}\",`);\n  if (config.target) lines.push(`        target: \"${config.target}\",`);\n  if (config.flowId) lines.push(`        flowId: \"${config.flowId}\",`);\n  if (shouldEmitParserType) lines.push(`        parserType: \"${parserType}\",`);\n\n  if (config.theme && typeof config.theme === \"object\" && Object.keys(config.theme).length > 0) {\n    appendSerializableObjectBlock(lines, \"theme\", config.theme as Record<string, unknown>, \"        \");\n  }\n\n  if (config.launcher) {\n    appendSerializableObjectBlock(lines, \"launcher\", config.launcher, \"        \");\n  }\n\n  if (config.copy) {\n    lines.push(\"        copy: {\");\n    Object.entries(config.copy).forEach(([key, value]) => {\n      lines.push(`          ${key}: \"${value}\",`);\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.sendButton) {\n    lines.push(\"        sendButton: {\");\n    Object.entries(config.sendButton).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.voiceRecognition) {\n    lines.push(\"        voiceRecognition: {\");\n    Object.entries(config.voiceRecognition).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      } else if (typeof value === \"number\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.statusIndicator) {\n    lines.push(\"        statusIndicator: {\");\n    Object.entries(config.statusIndicator).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.features) {\n    lines.push(\"        features: {\");\n    Object.entries(config.features).forEach(([key, value]) => {\n      lines.push(`          ${key}: ${value},`);\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.suggestionChips && config.suggestionChips.length > 0) {\n    lines.push(\"        suggestionChips: [\");\n    config.suggestionChips.forEach((chip: string) => {\n      lines.push(`          \"${chip}\",`);\n    });\n    lines.push(\"        ],\");\n  }\n\n  if (config.suggestionChipsConfig) {\n    lines.push(\"        suggestionChipsConfig: {\");\n    if (config.suggestionChipsConfig.fontFamily) {\n      lines.push(`          fontFamily: \"${config.suggestionChipsConfig.fontFamily}\",`);\n    }\n    if (config.suggestionChipsConfig.fontWeight) {\n      lines.push(`          fontWeight: \"${config.suggestionChipsConfig.fontWeight}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingX) {\n      lines.push(`          paddingX: \"${config.suggestionChipsConfig.paddingX}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingY) {\n      lines.push(`          paddingY: \"${config.suggestionChipsConfig.paddingY}\",`);\n    }\n    lines.push(\"        },\");\n  }\n\n  // Add toolCall config\n  lines.push(...generateToolCallConfig(config, \"        \"));\n\n  // Add messageActions config (with hook callbacks if provided)\n  lines.push(...generateMessageActionsConfig(config, \"        \", hooks));\n\n  // Add markdown config\n  lines.push(...generateMarkdownConfig(config, \"        \"));\n\n  // Add layout config\n  lines.push(...generateLayoutConfig(config, \"        \"));\n\n  // Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)\n  lines.push(...generateHooksConfig(hooks, \"        \"));\n\n  if (config.debug) {\n    lines.push(`        debug: ${config.debug},`);\n  }\n\n  // Use custom postprocessMessage if provided, otherwise default\n  if (hooks?.postprocessMessage) {\n    lines.push(`        postprocessMessage: ${hooks.postprocessMessage}`);\n  } else {\n    lines.push(\"        postprocessMessage: ({ text }) => markdownPostprocessor(text)\");\n  }\n  lines.push(\"      }\");\n  lines.push(\"    });\");\n  lines.push(\"\");\n  lines.push(\"    // Cleanup on unmount\");\n  lines.push(\"    return () => {\");\n  lines.push(\"      if (handle) {\");\n  lines.push(\"        handle.destroy();\");\n  lines.push(\"      }\");\n  lines.push(\"    };\");\n  lines.push(\"  }, []);\");\n  lines.push(\"\");\n  lines.push(\"  return null; // Widget injects itself into the DOM\");\n  lines.push(\"}\");\n  lines.push(\"\");\n  lines.push(\"// Usage in your app:\");\n  lines.push(\"// import { ChatWidget } from './components/ChatWidget';\");\n  lines.push(\"//\");\n  lines.push(\"// export default function App() {\");\n  lines.push(\"//   return (\");\n  lines.push(\"//     <div>\");\n  lines.push(\"//       {/* Your app content */}\");\n  lines.push(\"//       <ChatWidget />\");\n  lines.push(\"//     </div>\");\n  lines.push(\"//   );\");\n  lines.push(\"// }\");\n\n  return lines.join(\"\\n\");\n}\n\nfunction generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions): string {\n  const hooks = options?.hooks;\n  const lines: string[] = [\n    \"// ChatWidgetAdvanced.tsx\",\n    \"'use client'; // Required for Next.js - remove for Vite/CRA\",\n    \"\",\n    \"import { useEffect } from 'react';\",\n    \"import '@runtypelabs/persona/widget.css';\",\n    \"import {\",\n    \"  initAgentWidget,\",\n    \"  createFlexibleJsonStreamParser,\",\n    \"  defaultJsonActionParser,\",\n    \"  defaultActionHandlers,\",\n    \"  markdownPostprocessor\",\n    \"} from '@runtypelabs/persona';\",\n    \"import type { AgentWidgetInitHandle } from '@runtypelabs/persona';\",\n    \"\",\n    \"const STORAGE_KEY = 'chat-widget-state';\",\n    \"const PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';\",\n    \"\",\n    \"// Types for DOM elements\",\n    \"interface PageElement {\",\n    \"  type: string;\",\n    \"  tagName: string;\",\n    \"  selector: string;\",\n    \"  innerText: string;\",\n    \"  href?: string;\",\n    \"}\",\n    \"\",\n    \"interface DOMContext {\",\n    \"  page_elements: PageElement[];\",\n    \"  page_element_count: number;\",\n    \"  element_types: Record<string, number>;\",\n    \"  page_url: string;\",\n    \"  page_title: string;\",\n    \"  timestamp: string;\",\n    \"}\",\n    \"\",\n    \"// DOM context provider - extracts page elements for AI context\",\n    \"const collectDOMContext = (): DOMContext => {\",\n    \"  const selectors = {\",\n    \"    products: '[data-product-id], .product-card, .product-item, [role=\\\"article\\\"]',\",\n    \"    buttons: 'button, [role=\\\"button\\\"], .btn',\",\n    \"    links: 'a[href]',\",\n    \"    inputs: 'input, textarea, select'\",\n    \"  };\",\n    \"\",\n    \"  const elements: PageElement[] = [];\",\n    \"  Object.entries(selectors).forEach(([type, selector]) => {\",\n    \"    document.querySelectorAll(selector).forEach((element) => {\",\n    \"      if (!(element instanceof HTMLElement)) return;\",\n    \"      \",\n    \"      // Exclude elements within the widget\",\n    \"      const widgetHost = element.closest('.persona-host');\",\n    \"      if (widgetHost) return;\",\n    \"      \",\n    \"      const text = element.innerText?.trim();\",\n    \"      if (!text) return;\",\n    \"\",\n    \"      const selectorString =\",\n    \"        element.id ? `#${element.id}` :\",\n    \"        element.getAttribute('data-testid') ? `[data-testid=\\\"${element.getAttribute('data-testid')}\\\"]` :\",\n    \"        element.getAttribute('data-product-id') ? `[data-product-id=\\\"${element.getAttribute('data-product-id')}\\\"]` :\",\n    \"        element.tagName.toLowerCase();\",\n    \"\",\n    \"      const elementData: PageElement = {\",\n    \"        type,\",\n    \"        tagName: element.tagName.toLowerCase(),\",\n    \"        selector: selectorString,\",\n    \"        innerText: text.substring(0, 200)\",\n    \"      };\",\n    \"\",\n    \"      if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {\",\n    \"        elementData.href = element.href;\",\n    \"      }\",\n    \"\",\n    \"      elements.push(elementData);\",\n    \"    });\",\n    \"  });\",\n    \"\",\n    \"  const counts = elements.reduce((acc, el) => {\",\n    \"    acc[el.type] = (acc[el.type] || 0) + 1;\",\n    \"    return acc;\",\n    \"  }, {} as Record<string, number>);\",\n    \"\",\n    \"  return {\",\n    \"    page_elements: elements.slice(0, 50),\",\n    \"    page_element_count: elements.length,\",\n    \"    element_types: counts,\",\n    \"    page_url: window.location.href,\",\n    \"    page_title: document.title,\",\n    \"    timestamp: new Date().toISOString()\",\n    \"  };\",\n    \"};\",\n    \"\",\n    \"export function ChatWidgetAdvanced() {\",\n    \"  useEffect(() => {\",\n    \"    let handle: AgentWidgetInitHandle | null = null;\",\n    \"\",\n    \"    // Load saved state\",\n    \"    const loadSavedMessages = () => {\",\n    \"      const savedState = localStorage.getItem(STORAGE_KEY);\",\n    \"      if (savedState) {\",\n    \"        try {\",\n    \"          const { messages } = JSON.parse(savedState);\",\n    \"          return messages || [];\",\n    \"        } catch (e) {\",\n    \"          console.error('Failed to load saved state:', e);\",\n    \"        }\",\n    \"      }\",\n    \"      return [];\",\n    \"    };\",\n    \"\",\n    \"    handle = initAgentWidget({\",\n    `      target: '${emitTargetSelector(options)}',`,\n    \"      config: {\"\n  ];\n\n  if (config.apiUrl) lines.push(`        apiUrl: \"${config.apiUrl}\",`);\n  if (config.clientToken) lines.push(`        clientToken: \"${config.clientToken}\",`);\n  if (config.agentId) lines.push(`        agentId: \"${config.agentId}\",`);\n  if (config.target) lines.push(`        target: \"${config.target}\",`);\n  if (config.flowId) lines.push(`        flowId: \"${config.flowId}\",`);\n\n  if (config.theme && typeof config.theme === \"object\" && Object.keys(config.theme).length > 0) {\n    appendSerializableObjectBlock(lines, \"theme\", config.theme as Record<string, unknown>, \"        \");\n  }\n\n  if (config.launcher) {\n    appendSerializableObjectBlock(lines, \"launcher\", config.launcher, \"        \");\n  }\n\n  if (config.copy) {\n    lines.push(\"        copy: {\");\n    Object.entries(config.copy).forEach(([key, value]) => {\n      lines.push(`          ${key}: \"${value}\",`);\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.sendButton) {\n    lines.push(\"        sendButton: {\");\n    Object.entries(config.sendButton).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.voiceRecognition) {\n    lines.push(\"        voiceRecognition: {\");\n    Object.entries(config.voiceRecognition).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      } else if (typeof value === \"number\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.statusIndicator) {\n    lines.push(\"        statusIndicator: {\");\n    Object.entries(config.statusIndicator).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`          ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`          ${key}: ${value},`);\n      }\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.features) {\n    lines.push(\"        features: {\");\n    Object.entries(config.features).forEach(([key, value]) => {\n      lines.push(`          ${key}: ${value},`);\n    });\n    lines.push(\"        },\");\n  }\n\n  if (config.suggestionChips && config.suggestionChips.length > 0) {\n    lines.push(\"        suggestionChips: [\");\n    config.suggestionChips.forEach((chip: string) => {\n      lines.push(`          \"${chip}\",`);\n    });\n    lines.push(\"        ],\");\n  }\n\n  if (config.suggestionChipsConfig) {\n    lines.push(\"        suggestionChipsConfig: {\");\n    if (config.suggestionChipsConfig.fontFamily) {\n      lines.push(`          fontFamily: \"${config.suggestionChipsConfig.fontFamily}\",`);\n    }\n    if (config.suggestionChipsConfig.fontWeight) {\n      lines.push(`          fontWeight: \"${config.suggestionChipsConfig.fontWeight}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingX) {\n      lines.push(`          paddingX: \"${config.suggestionChipsConfig.paddingX}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingY) {\n      lines.push(`          paddingY: \"${config.suggestionChipsConfig.paddingY}\",`);\n    }\n    lines.push(\"        },\");\n  }\n\n  // Add toolCall config\n  lines.push(...generateToolCallConfig(config, \"        \"));\n\n  // Add messageActions config (with hook callbacks if provided)\n  lines.push(...generateMessageActionsConfig(config, \"        \", hooks));\n\n  // Add markdown config\n  lines.push(...generateMarkdownConfig(config, \"        \"));\n\n  // Add layout config\n  lines.push(...generateLayoutConfig(config, \"        \"));\n\n  // Add getHeaders if provided\n  if (hooks?.getHeaders) {\n    lines.push(`        getHeaders: ${hooks.getHeaders},`);\n  }\n\n  // Add contextProviders if provided\n  if (hooks?.contextProviders) {\n    lines.push(`        contextProviders: ${hooks.contextProviders},`);\n  }\n\n  if (config.debug) {\n    lines.push(`        debug: ${config.debug},`);\n  }\n\n  lines.push(\"        initialMessages: loadSavedMessages(),\");\n\n  // Stream parser - use custom if provided, otherwise default\n  if (hooks?.streamParser) {\n    lines.push(`        streamParser: ${hooks.streamParser},`);\n  } else {\n    lines.push(\"        // Flexible JSON stream parser for handling structured actions\");\n    lines.push(`        streamParser: () => createFlexibleJsonStreamParser(${TEMPLATE_STREAM_PARSER_CALLBACK_TS}),`);\n  }\n\n  // Action parsers - merge custom with defaults if provided\n  if (hooks?.actionParsers) {\n    lines.push(\"        // Action parsers (custom merged with defaults)\");\n    lines.push(`        actionParsers: [...(${hooks.actionParsers}), defaultJsonActionParser,`);\n    lines.push(`          // Built-in parser for markdown-wrapped JSON`);\n    lines.push(`          ${TEMPLATE_MARKDOWN_JSON_PARSER_TS}`);\n    lines.push(\"        ],\");\n  } else {\n    lines.push(\"        // Action parsers to detect JSON actions in responses\");\n    lines.push(\"        actionParsers: [\");\n    lines.push(\"          defaultJsonActionParser,\");\n    lines.push(`          // Parser for markdown-wrapped JSON`);\n    lines.push(`          ${TEMPLATE_MARKDOWN_JSON_PARSER_TS}`);\n    lines.push(\"        ],\");\n  }\n\n  // Action handlers - merge custom with defaults if provided\n  if (hooks?.actionHandlers) {\n    lines.push(\"        // Action handlers (custom merged with defaults)\");\n    lines.push(`        actionHandlers: [...(${hooks.actionHandlers}),`);\n    lines.push(\"          defaultActionHandlers.message,\");\n    lines.push(\"          defaultActionHandlers.messageAndClick,\");\n    lines.push(`          // Built-in handler for nav_then_click action`);\n    lines.push(`          ${TEMPLATE_NAV_THEN_CLICK_HANDLER_TS}`);\n    lines.push(\"        ],\");\n  } else {\n    lines.push(\"        // Action handlers for navigation and other actions\");\n    lines.push(\"        actionHandlers: [\");\n    lines.push(\"          defaultActionHandlers.message,\");\n    lines.push(\"          defaultActionHandlers.messageAndClick,\");\n    lines.push(`          // Handler for nav_then_click action`);\n    lines.push(`          ${TEMPLATE_NAV_THEN_CLICK_HANDLER_TS}`);\n    lines.push(\"        ],\");\n  }\n\n  // postprocessMessage - use custom if provided, otherwise default\n  if (hooks?.postprocessMessage) {\n    lines.push(`        postprocessMessage: ${hooks.postprocessMessage},`);\n  } else {\n    lines.push(\"        postprocessMessage: ({ text }) => markdownPostprocessor(text),\");\n  }\n\n  // requestMiddleware - merge custom with DOM context if provided\n  if (hooks?.requestMiddleware) {\n    lines.push(\"        // Request middleware (custom merged with DOM context)\");\n    lines.push(\"        requestMiddleware: ({ payload, config }) => {\");\n    lines.push(`          const customResult = (${hooks.requestMiddleware})({ payload, config });`);\n    lines.push(\"          const merged = customResult || payload;\");\n    lines.push(\"          return {\");\n    lines.push(\"            ...merged,\");\n    lines.push(\"            metadata: { ...merged.metadata, ...collectDOMContext() }\");\n    lines.push(\"          };\");\n    lines.push(\"        }\");\n  } else {\n    lines.push(\"        requestMiddleware: ({ payload }) => {\");\n    lines.push(\"          return {\");\n    lines.push(\"            ...payload,\");\n    lines.push(\"            metadata: collectDOMContext()\");\n    lines.push(\"          };\");\n    lines.push(\"        }\");\n  }\n  lines.push(\"      }\");\n  lines.push(\"    });\");\n  lines.push(\"\");\n  lines.push(\"    // Save state on message events\");\n  lines.push(\"    const handleMessage = () => {\");\n  lines.push(\"      const session = handle?.getSession?.();\");\n  lines.push(\"      if (session) {\");\n  lines.push(\"        localStorage.setItem(STORAGE_KEY, JSON.stringify({\");\n  lines.push(\"          messages: session.messages,\");\n  lines.push(\"          timestamp: new Date().toISOString()\");\n  lines.push(\"        }));\");\n  lines.push(\"      }\");\n  lines.push(\"    };\");\n  lines.push(\"\");\n  lines.push(\"    // Clear state on clear chat\");\n  lines.push(\"    const handleClearChat = () => {\");\n  lines.push(\"      localStorage.removeItem(STORAGE_KEY);\");\n  lines.push(\"      localStorage.removeItem(PROCESSED_ACTIONS_KEY);\");\n  lines.push(\"    };\");\n  lines.push(\"\");\n  lines.push(\"    window.addEventListener('persona:message', handleMessage);\");\n  lines.push(\"    window.addEventListener('persona:clear-chat', handleClearChat);\");\n  lines.push(\"\");\n  lines.push(\"    // Cleanup on unmount\");\n  lines.push(\"    return () => {\");\n  lines.push(\"      window.removeEventListener('persona:message', handleMessage);\");\n  lines.push(\"      window.removeEventListener('persona:clear-chat', handleClearChat);\");\n  lines.push(\"      if (handle) {\");\n  lines.push(\"        handle.destroy();\");\n  lines.push(\"      }\");\n  lines.push(\"    };\");\n  lines.push(\"  }, []);\");\n  lines.push(\"\");\n  lines.push(\"  return null; // Widget injects itself into the DOM\");\n  lines.push(\"}\");\n  lines.push(\"\");\n  lines.push(\"// Usage: Collects DOM context for AI-powered navigation\");\n  lines.push(\"// Features:\");\n  lines.push(\"// - Extracts page elements (products, buttons, links)\");\n  lines.push(\"// - Persists chat history across page loads\");\n  lines.push(\"// - Handles navigation actions (nav_then_click)\");\n  lines.push(\"// - Processes structured JSON actions from AI\");\n  lines.push(\"//\");\n  lines.push(\"// Example usage in Next.js:\");\n  lines.push(\"// import { ChatWidgetAdvanced } from './components/ChatWidgetAdvanced';\");\n  lines.push(\"//\");\n  lines.push(\"// export default function RootLayout({ children }) {\");\n  lines.push(\"//   return (\");\n  lines.push(\"//     <html lang=\\\"en\\\">\");\n  lines.push(\"//       <body>\");\n  lines.push(\"//         {children}\");\n  lines.push(\"//         <ChatWidgetAdvanced />\");\n  lines.push(\"//       </body>\");\n  lines.push(\"//     </html>\");\n  lines.push(\"//   );\");\n  lines.push(\"// }\");\n\n  return lines.join(\"\\n\");\n}\n\n// Helper to build a serializable config object for JSON export\nfunction buildSerializableConfig(config: any): Record<string, any> {\n  const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);\n  const shouldEmitParserType = parserType !== \"plain\";\n  \n  const serializableConfig: Record<string, any> = {};\n  \n  if (config.apiUrl) serializableConfig.apiUrl = config.apiUrl;\n  if (config.clientToken) serializableConfig.clientToken = config.clientToken;\n  if (config.agentId) serializableConfig.agentId = config.agentId;\n  if (config.target) serializableConfig.target = config.target;\n  if (config.flowId) serializableConfig.flowId = config.flowId;\n  if (shouldEmitParserType) serializableConfig.parserType = parserType;\n  if (config.theme) serializableConfig.theme = config.theme;\n  if (config.launcher) serializableConfig.launcher = config.launcher;\n  if (config.copy) serializableConfig.copy = config.copy;\n  if (config.sendButton) serializableConfig.sendButton = config.sendButton;\n  if (config.voiceRecognition) serializableConfig.voiceRecognition = config.voiceRecognition;\n  if (config.statusIndicator) serializableConfig.statusIndicator = config.statusIndicator;\n  if (config.features) serializableConfig.features = config.features;\n  if (config.suggestionChips?.length > 0) serializableConfig.suggestionChips = config.suggestionChips;\n  if (config.suggestionChipsConfig) serializableConfig.suggestionChipsConfig = config.suggestionChipsConfig;\n  if (config.debug) serializableConfig.debug = config.debug;\n  \n  // Add toolCall config (only serializable parts)\n  if (config.toolCall) {\n    const toolCallConfig: Record<string, any> = {};\n    Object.entries(config.toolCall).forEach(([key, value]) => {\n      if (typeof value === \"string\") toolCallConfig[key] = value;\n    });\n    if (Object.keys(toolCallConfig).length > 0) {\n      serializableConfig.toolCall = toolCallConfig;\n    }\n  }\n  \n  // Add messageActions config (excluding callbacks)\n  if (config.messageActions) {\n    const messageActionsConfig: Record<string, any> = {};\n    Object.entries(config.messageActions).forEach(([key, value]) => {\n      if (key !== \"onFeedback\" && key !== \"onCopy\" && value !== undefined) {\n        if (typeof value === \"string\" || typeof value === \"boolean\") {\n          messageActionsConfig[key] = value;\n        }\n      }\n    });\n    if (Object.keys(messageActionsConfig).length > 0) {\n      serializableConfig.messageActions = messageActionsConfig;\n    }\n  }\n  \n  // Add markdown config (excluding renderer functions)\n  if (config.markdown) {\n    const markdownConfig: Record<string, any> = {};\n    if (config.markdown.options) markdownConfig.options = config.markdown.options;\n    if (config.markdown.disableDefaultStyles !== undefined) {\n      markdownConfig.disableDefaultStyles = config.markdown.disableDefaultStyles;\n    }\n    if (Object.keys(markdownConfig).length > 0) {\n      serializableConfig.markdown = markdownConfig;\n    }\n  }\n  \n  // Add layout config (excluding render functions)\n  if (config.layout) {\n    const layoutConfig: Record<string, any> = {};\n    \n    if (config.layout.header) {\n      const headerConfig: Record<string, any> = {};\n      Object.entries(config.layout.header).forEach(([key, value]) => {\n        if (key !== \"render\" && (typeof value === \"string\" || typeof value === \"boolean\")) {\n          headerConfig[key] = value;\n        }\n      });\n      if (Object.keys(headerConfig).length > 0) {\n        layoutConfig.header = headerConfig;\n      }\n    }\n    \n    if (config.layout.messages) {\n      const messagesConfig: Record<string, any> = {};\n      Object.entries(config.layout.messages).forEach(([key, value]) => {\n        if (key !== \"renderUserMessage\" && key !== \"renderAssistantMessage\") {\n          if (key === \"avatar\" && typeof value === \"object\" && value !== null) {\n            messagesConfig.avatar = value;\n          } else if (key === \"timestamp\" && typeof value === \"object\" && value !== null) {\n            // Exclude format function\n            const tsConfig: Record<string, any> = {};\n            Object.entries(value as Record<string, unknown>).forEach(([tsKey, tsValue]) => {\n              if (tsKey !== \"format\" && (typeof tsValue === \"string\" || typeof tsValue === \"boolean\")) {\n                tsConfig[tsKey] = tsValue;\n              }\n            });\n            if (Object.keys(tsConfig).length > 0) {\n              messagesConfig.timestamp = tsConfig;\n            }\n          } else if (typeof value === \"string\" || typeof value === \"boolean\") {\n            messagesConfig[key] = value;\n          }\n        }\n      });\n      if (Object.keys(messagesConfig).length > 0) {\n        layoutConfig.messages = messagesConfig;\n      }\n    }\n    \n    if (Object.keys(layoutConfig).length > 0) {\n      serializableConfig.layout = layoutConfig;\n    }\n  }\n  \n  return serializableConfig;\n}\n\nfunction generateScriptInstallerCode(config: any, options?: CodeGeneratorOptions): string {\n  const serializableConfig = buildSerializableConfig(config);\n\n  // `windowKey` and `target` are INSTALLER options, not widget-config fields.\n  // install.ts reads them from the top level of data-config: the nested-`config`\n  // branch hoists every sibling key (Object.assign(scriptConfig, parsed)), while\n  // a flat payload becomes the widget config wholesale and its siblings are\n  // ignored. So whenever either is set we emit the nested `{ config, ...}` form\n  // with the option as a SIBLING of `config`: nesting `target` inside the\n  // widget config would leave the widget mounted on `body`.\n  const needsWrapper = Boolean(options?.windowKey || options?.target);\n  const payload = needsWrapper\n    ? {\n        config: serializableConfig,\n        ...(options?.windowKey ? { windowKey: options.windowKey } : {}),\n        ...(options?.target ? { target: options.target } : {}),\n      }\n    : serializableConfig;\n\n  // Escape single quotes in JSON for HTML attribute\n  const configJson = JSON.stringify(payload, null, 0).replace(/'/g, \"&#39;\");\n\n  return `<script src=\"https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${VERSION}/dist/install.global.js\" data-config='${configJson}'></script>`;\n}\n\nfunction generateScriptManualCode(config: any, options?: CodeGeneratorOptions): string {\n  const hooks = options?.hooks;\n  const parserType = getParserTypeFromConfig(config as AgentWidgetConfig);\n  const shouldEmitParserType = parserType !== \"plain\";\n\n  const lines: string[] = [\n    \"<!-- Load CSS -->\",\n    `<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${VERSION}/dist/widget.css\" />`,\n    \"\",\n    \"<!-- Load JavaScript -->\",\n    `<script src=\"https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${VERSION}/dist/index.global.js\"></script>`,\n    \"\",\n    \"<!-- Initialize widget -->\",\n    \"<script>\",\n    \"  var handle = window.AgentWidget.initAgentWidget({\",\n    `    target: '${emitTargetSelector(options)}',`,\n    ...(options?.windowKey ? [`    windowKey: '${options.windowKey}',`] : []),\n    \"    config: {\"\n  ];\n\n  if (config.apiUrl) lines.push(`      apiUrl: \"${config.apiUrl}\",`);\n  if (config.clientToken) lines.push(`      clientToken: \"${config.clientToken}\",`);\n  if (config.agentId) lines.push(`      agentId: \"${config.agentId}\",`);\n  if (config.target) lines.push(`      target: \"${config.target}\",`);\n  if (config.flowId) lines.push(`      flowId: \"${config.flowId}\",`);\n  if (shouldEmitParserType) lines.push(`      parserType: \"${parserType}\",`);\n\n  if (config.theme && typeof config.theme === \"object\" && Object.keys(config.theme).length > 0) {\n    appendSerializableObjectBlock(lines, \"theme\", config.theme as Record<string, unknown>, \"      \");\n  }\n\n  if (config.launcher) {\n    appendSerializableObjectBlock(lines, \"launcher\", config.launcher, \"      \");\n  }\n\n  if (config.copy) {\n    lines.push(\"      copy: {\");\n    Object.entries(config.copy).forEach(([key, value]) => {\n      lines.push(`        ${key}: \"${value}\",`);\n    });\n    lines.push(\"      },\");\n  }\n\n  if (config.sendButton) {\n    lines.push(\"      sendButton: {\");\n    Object.entries(config.sendButton).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`        ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`        ${key}: ${value},`);\n      }\n    });\n    lines.push(\"      },\");\n  }\n\n  if (config.voiceRecognition) {\n    lines.push(\"      voiceRecognition: {\");\n    Object.entries(config.voiceRecognition).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`        ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`        ${key}: ${value},`);\n      } else if (typeof value === \"number\") {\n        lines.push(`        ${key}: ${value},`);\n      }\n    });\n    lines.push(\"      },\");\n  }\n\n  if (config.statusIndicator) {\n    lines.push(\"      statusIndicator: {\");\n    Object.entries(config.statusIndicator).forEach(([key, value]) => {\n      if (typeof value === \"string\") {\n        lines.push(`        ${key}: \"${value}\",`);\n      } else if (typeof value === \"boolean\") {\n        lines.push(`        ${key}: ${value},`);\n      }\n    });\n    lines.push(\"      },\");\n  }\n\n  if (config.features) {\n    lines.push(\"      features: {\");\n    Object.entries(config.features).forEach(([key, value]) => {\n      lines.push(`        ${key}: ${value},`);\n    });\n    lines.push(\"      },\");\n  }\n\n  if (config.suggestionChips && config.suggestionChips.length > 0) {\n    lines.push(\"      suggestionChips: [\");\n    config.suggestionChips.forEach((chip: string) => {\n      lines.push(`        \"${chip}\",`);\n    });\n    lines.push(\"      ],\");\n  }\n\n  if (config.suggestionChipsConfig) {\n    lines.push(\"      suggestionChipsConfig: {\");\n    if (config.suggestionChipsConfig.fontFamily) {\n      lines.push(`        fontFamily: \"${config.suggestionChipsConfig.fontFamily}\",`);\n    }\n    if (config.suggestionChipsConfig.fontWeight) {\n      lines.push(`        fontWeight: \"${config.suggestionChipsConfig.fontWeight}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingX) {\n      lines.push(`        paddingX: \"${config.suggestionChipsConfig.paddingX}\",`);\n    }\n    if (config.suggestionChipsConfig.paddingY) {\n      lines.push(`        paddingY: \"${config.suggestionChipsConfig.paddingY}\",`);\n    }\n    lines.push(\"      },\");\n  }\n\n  // Add toolCall config\n  lines.push(...generateToolCallConfig(config, \"      \"));\n\n  // Add messageActions config (with hook callbacks if provided)\n  lines.push(...generateMessageActionsConfig(config, \"      \", hooks));\n\n  // Add markdown config\n  lines.push(...generateMarkdownConfig(config, \"      \"));\n\n  // Add layout config\n  lines.push(...generateLayoutConfig(config, \"      \"));\n\n  // Add hook-based config (getHeaders, requestMiddleware, actionParsers, actionHandlers, etc.)\n  lines.push(...generateHooksConfig(hooks, \"      \"));\n\n  if (config.debug) {\n    lines.push(`      debug: ${config.debug},`);\n  }\n\n  // Use custom postprocessMessage if provided, otherwise default\n  if (hooks?.postprocessMessage) {\n    lines.push(`      postprocessMessage: ${hooks.postprocessMessage}`);\n  } else {\n    lines.push(\"      postprocessMessage: ({ text }) => window.AgentWidget.markdownPostprocessor(text)\");\n  }\n  lines.push(\"    }\");\n  lines.push(\"  });\");\n  lines.push(\"</script>\");\n\n  return lines.join(\"\\n\");\n}\n\nfunction generateScriptAdvancedCode(config: any, options?: CodeGeneratorOptions): string {\n  const hooks = options?.hooks;\n  const serializableConfig = buildSerializableConfig(config);\n  const configJson = JSON.stringify(serializableConfig, null, 2);\n\n  const lines: string[] = [\n    \"<script>\",\n    \"(function() {\",\n    \"  'use strict';\",\n    \"\",\n    \"  // Configuration\",\n    `  var CONFIG = ${configJson.split('\\n').map((line, i) => i === 0 ? line : '  ' + line).join('\\n')};`,\n    \"\",\n    \"  // Constants\",\n    `  var CDN_BASE = 'https://cdn.jsdelivr.net/npm/@runtypelabs/persona@${VERSION}/dist';`,\n    \"  var STORAGE_KEY = 'chat-widget-state';\",\n    \"  var PROCESSED_ACTIONS_KEY = 'chat-widget-processed-actions';\",\n    \"\",\n    \"  // DOM context provider - extracts page elements for AI context\",\n    \"  var domContextProvider = function() {\",\n    \"    var selectors = {\",\n    \"      products: '[data-product-id], .product-card, .product-item, [role=\\\"article\\\"]',\",\n    \"      buttons: 'button, [role=\\\"button\\\"], .btn',\",\n    \"      links: 'a[href]',\",\n    \"      inputs: 'input, textarea, select'\",\n    \"    };\",\n    \"\",\n    \"    var elements = [];\",\n    \"    Object.entries(selectors).forEach(function(entry) {\",\n    \"      var type = entry[0], selector = entry[1];\",\n    \"      document.querySelectorAll(selector).forEach(function(element) {\",\n    \"        if (!(element instanceof HTMLElement)) return;\",\n    \"        var widgetHost = element.closest('.persona-host');\",\n    \"        if (widgetHost) return;\",\n    \"        var text = element.innerText ? element.innerText.trim() : '';\",\n    \"        if (!text) return;\",\n    \"\",\n    \"        var selectorString = element.id ? '#' + element.id :\",\n    \"          element.getAttribute('data-testid') ? '[data-testid=\\\"' + element.getAttribute('data-testid') + '\\\"]' :\",\n    \"          element.getAttribute('data-product-id') ? '[data-product-id=\\\"' + element.getAttribute('data-product-id') + '\\\"]' :\",\n    \"          element.tagName.toLowerCase();\",\n    \"\",\n    \"        var elementData = {\",\n    \"          type: type,\",\n    \"          tagName: element.tagName.toLowerCase(),\",\n    \"          selector: selectorString,\",\n    \"          innerText: text.substring(0, 200)\",\n    \"        };\",\n    \"\",\n    \"        if (type === 'links' && element instanceof HTMLAnchorElement && element.href) {\",\n    \"          elementData.href = element.href;\",\n    \"        }\",\n    \"        elements.push(elementData);\",\n    \"      });\",\n    \"    });\",\n    \"\",\n    \"    var counts = elements.reduce(function(acc, el) {\",\n    \"      acc[el.type] = (acc[el.type] || 0) + 1;\",\n    \"      return acc;\",\n    \"    }, {});\",\n    \"\",\n    \"    return {\",\n    \"      page_elements: elements.slice(0, 50),\",\n    \"      page_element_count: elements.length,\",\n    \"      element_types: counts,\",\n    \"      page_url: window.location.href,\",\n    \"      page_title: document.title,\",\n    \"      timestamp: new Date().toISOString()\",\n    \"    };\",\n    \"  };\",\n    \"\",\n    \"  // Load CSS dynamically\",\n    \"  var loadCSS = function() {\",\n    \"    if (document.querySelector('link[data-persona]')) return;\",\n    \"    var link = document.createElement('link');\",\n    \"    link.rel = 'stylesheet';\",\n    \"    link.href = CDN_BASE + '/widget.css';\",\n    \"    link.setAttribute('data-persona', 'true');\",\n    \"    document.head.appendChild(link);\",\n    \"  };\",\n    \"\",\n    \"  // Load JS dynamically\",\n    \"  var loadJS = function(callback) {\",\n    \"    if (window.AgentWidget) { callback(); return; }\",\n    \"    var script = document.createElement('script');\",\n    \"    script.src = CDN_BASE + '/index.global.js';\",\n    \"    script.onload = callback;\",\n    \"    script.onerror = function() { console.error('Failed to load AgentWidget'); };\",\n    \"    document.head.appendChild(script);\",\n    \"  };\",\n    \"\",\n    \"  // Create widget config with advanced features\",\n    \"  var createWidgetConfig = function(agentWidget) {\",\n    \"    var widgetConfig = Object.assign({}, CONFIG);\",\n    \"\"\n  ];\n\n  // Add getHeaders if provided\n  if (hooks?.getHeaders) {\n    lines.push(`    widgetConfig.getHeaders = ${hooks.getHeaders};`);\n    lines.push(\"\");\n  }\n\n  // Add contextProviders if provided\n  if (hooks?.contextProviders) {\n    lines.push(`    widgetConfig.contextProviders = ${hooks.contextProviders};`);\n    lines.push(\"\");\n  }\n\n  // Stream parser - use custom if provided, otherwise default\n  if (hooks?.streamParser) {\n    lines.push(`    widgetConfig.streamParser = ${hooks.streamParser};`);\n  } else {\n    lines.push(\"    // Flexible JSON stream parser for handling structured actions\");\n    lines.push(\"    widgetConfig.streamParser = function() {\");\n    lines.push(`      return agentWidget.createFlexibleJsonStreamParser(${TEMPLATE_STREAM_PARSER_CALLBACK_ES5});`);\n    lines.push(\"    };\");\n  }\n  lines.push(\"\");\n\n  // Action parsers - merge custom with defaults if provided\n  if (hooks?.actionParsers) {\n    lines.push(\"    // Action parsers (custom merged with defaults)\");\n    lines.push(`    var customParsers = ${hooks.actionParsers};`);\n    lines.push(\"    widgetConfig.actionParsers = customParsers.concat([\");\n    lines.push(\"      agentWidget.defaultJsonActionParser,\");\n    lines.push(`      ${TEMPLATE_MARKDOWN_JSON_PARSER_ES5}`);\n    lines.push(\"    ]);\");\n  } else {\n    lines.push(\"    // Action parsers to detect JSON actions in responses\");\n    lines.push(\"    widgetConfig.actionParsers = [\");\n    lines.push(\"      agentWidget.defaultJsonActionParser,\");\n    lines.push(`      ${TEMPLATE_MARKDOWN_JSON_PARSER_ES5}`);\n    lines.push(\"    ];\");\n  }\n  lines.push(\"\");\n\n  // Action handlers - merge custom with defaults if provided\n  if (hooks?.actionHandlers) {\n    lines.push(\"    // Action handlers (custom merged with defaults)\");\n    lines.push(`    var customHandlers = ${hooks.actionHandlers};`);\n    lines.push(\"    widgetConfig.actionHandlers = customHandlers.concat([\");\n    lines.push(\"      agentWidget.defaultActionHandlers.message,\");\n    lines.push(\"      agentWidget.defaultActionHandlers.messageAndClick,\");\n    lines.push(`      ${TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5}`);\n    lines.push(\"    ]);\");\n  } else {\n    lines.push(\"    // Action handlers for navigation and other actions\");\n    lines.push(\"    widgetConfig.actionHandlers = [\");\n    lines.push(\"      agentWidget.defaultActionHandlers.message,\");\n    lines.push(\"      agentWidget.defaultActionHandlers.messageAndClick,\");\n    lines.push(`      ${TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5}`);\n    lines.push(\"    ];\");\n  }\n  lines.push(\"\");\n\n  // requestMiddleware - merge custom with DOM context if provided\n  if (hooks?.requestMiddleware) {\n    lines.push(\"    // Request middleware (custom merged with DOM context)\");\n    lines.push(\"    widgetConfig.requestMiddleware = function(ctx) {\");\n    lines.push(`      var customResult = (${hooks.requestMiddleware})(ctx);`);\n    lines.push(\"      var merged = customResult || ctx.payload;\");\n    lines.push(\"      return Object.assign({}, merged, { metadata: Object.assign({}, merged.metadata, domContextProvider()) });\");\n    lines.push(\"    };\");\n  } else {\n    lines.push(\"    // Send DOM context with each request\");\n    lines.push(\"    widgetConfig.requestMiddleware = function(ctx) {\");\n    lines.push(\"      return Object.assign({}, ctx.payload, { metadata: domContextProvider() });\");\n    lines.push(\"    };\");\n  }\n  lines.push(\"\");\n\n  // postprocessMessage - use custom if provided, otherwise default\n  if (hooks?.postprocessMessage) {\n    lines.push(`    widgetConfig.postprocessMessage = ${hooks.postprocessMessage};`);\n  } else {\n    lines.push(\"    // Markdown postprocessor\");\n    lines.push(\"    widgetConfig.postprocessMessage = function(ctx) {\");\n    lines.push(\"      return agentWidget.markdownPostprocessor(ctx.text);\");\n    lines.push(\"    };\");\n  }\n  lines.push(\"\");\n\n  // Add messageActions callbacks if provided\n  if (hooks?.onFeedback || hooks?.onCopy) {\n    lines.push(\"    // Message action callbacks\");\n    lines.push(\"    widgetConfig.messageActions = widgetConfig.messageActions || {};\");\n    if (hooks?.onFeedback) {\n      lines.push(`    widgetConfig.messageActions.onFeedback = ${hooks.onFeedback};`);\n    }\n    if (hooks?.onCopy) {\n      lines.push(`    widgetConfig.messageActions.onCopy = ${hooks.onCopy};`);\n    }\n    lines.push(\"\");\n  }\n\n  lines.push(...[\n    \"    return widgetConfig;\",\n    \"  };\",\n    \"\",\n    \"  // Initialize widget\",\n    \"  var init = function() {\",\n    \"    var agentWidget = window.AgentWidget;\",\n    \"    if (!agentWidget) {\",\n    \"      console.error('AgentWidget not loaded');\",\n    \"      return;\",\n    \"    }\",\n    \"\",\n    \"    var widgetConfig = createWidgetConfig(agentWidget);\",\n    \"\",\n    \"    // Load saved state\",\n    \"    var savedState = localStorage.getItem(STORAGE_KEY);\",\n    \"    if (savedState) {\",\n    \"      try {\",\n    \"        var parsed = JSON.parse(savedState);\",\n    \"        widgetConfig.initialMessages = parsed.messages || [];\",\n    \"      } catch (e) {\",\n    \"        console.error('Failed to load saved state:', e);\",\n    \"      }\",\n    \"    }\",\n    \"\",\n    \"    // Initialize widget\",\n    \"    var handle = agentWidget.initAgentWidget({\",\n    `      target: '${emitTargetSelector(options)}',`,\n    \"      useShadowDom: false,\",\n    ...(options?.windowKey ? [`      windowKey: '${options.windowKey}',`] : []),\n    \"      config: widgetConfig\",\n    \"    });\",\n    \"\",\n    \"    // Save state on message events\",\n    \"    window.addEventListener('persona:message', function() {\",\n    \"      var session = handle.getSession ? handle.getSession() : null;\",\n    \"      if (session) {\",\n    \"        localStorage.setItem(STORAGE_KEY, JSON.stringify({\",\n    \"          messages: session.messages,\",\n    \"          timestamp: new Date().toISOString()\",\n    \"        }));\",\n    \"      }\",\n    \"    });\",\n    \"\",\n    \"    // Clear state on clear chat\",\n    \"    window.addEventListener('persona:clear-chat', function() {\",\n    \"      localStorage.removeItem(STORAGE_KEY);\",\n    \"      localStorage.removeItem(PROCESSED_ACTIONS_KEY);\",\n    \"    });\",\n    \"  };\",\n    \"\",\n    \"  // Wait for framework hydration to complete (Next.js, Nuxt, etc.)\",\n    \"  // This prevents the framework from removing dynamically added CSS during reconciliation\",\n    \"  var waitForHydration = function(callback) {\",\n    \"    var executed = false;\",\n    \"    \",\n    \"    var execute = function() {\",\n    \"      if (executed) return;\",\n    \"      executed = true;\",\n    \"      callback();\",\n    \"    };\",\n    \"\",\n    \"    var afterDom = function() {\",\n    \"      // Strategy 1: Use requestIdleCallback if available (best for detecting idle after hydration)\",\n    \"      if (typeof requestIdleCallback !== 'undefined') {\",\n    \"        requestIdleCallback(function() {\",\n    \"          // Double requestAnimationFrame ensures at least one full paint cycle completed\",\n    \"          requestAnimationFrame(function() {\",\n    \"            requestAnimationFrame(execute);\",\n    \"          });\",\n    \"        }, { timeout: 3000 }); // Max wait 3 seconds, then proceed anyway\",\n    \"      } else {\",\n    \"        // Strategy 2: Fallback for Safari (no requestIdleCallback)\",\n    \"        // 300ms is typically enough for hydration on most pages\",\n    \"        setTimeout(execute, 300);\",\n    \"      }\",\n    \"    };\",\n    \"\",\n    \"    if (document.readyState === 'loading') {\",\n    \"      document.addEventListener('DOMContentLoaded', afterDom);\",\n    \"    } else {\",\n    \"      // DOM already ready, but still wait for potential hydration\",\n    \"      afterDom();\",\n    \"    }\",\n    \"  };\",\n    \"\",\n    \"  // Boot sequence: wait for hydration, then load CSS and JS, then initialize\",\n    \"  // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS during reconciliation\",\n    \"  waitForHydration(function() {\",\n    \"    loadCSS();\",\n    \"    loadJS(function() {\",\n    \"      init();\",\n    \"    });\",\n    \"  });\",\n    \"})();\",\n    \"</script>\"\n  ]);\n\n  return lines.join(\"\\n\");\n}\n","import { createElement } from \"../utils/dom\";\nimport { createIconButton } from \"../utils/buttons\";\nimport { createToggleGroup } from \"../utils/buttons\";\nimport { renderLucideIcon } from \"../utils/icons\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DemoCarouselItem {\n  /** URL to load in the iframe (relative or absolute). */\n  url: string;\n  /** Display title shown in the toolbar. */\n  title: string;\n  /** Optional subtitle/description. */\n  description?: string;\n}\n\nexport interface DemoCarouselOptions {\n  /** Demo pages to cycle through. */\n  items: DemoCarouselItem[];\n  /** Initial item index. Default: 0. */\n  initialIndex?: number;\n  /** Initial device viewport. Default: 'desktop'. */\n  initialDevice?: \"desktop\" | \"mobile\";\n  /** Initial color scheme for the iframe wrapper. Default: 'light'. */\n  initialColorScheme?: \"light\" | \"dark\";\n  /** Show zoom +/- controls. Default: true. */\n  showZoomControls?: boolean;\n  /** Show desktop/mobile toggle. Default: true. */\n  showDeviceToggle?: boolean;\n  /** Show light/dark scheme toggle. Default: true. */\n  showColorSchemeToggle?: boolean;\n  /** Called when the active demo changes. */\n  onChange?: (index: number, item: DemoCarouselItem) => void;\n}\n\nexport interface DemoCarouselHandle {\n  /** Root element (already appended to the container). */\n  element: HTMLElement;\n  /** Navigate to a demo by index. */\n  goTo(index: number): void;\n  /** Go to the next demo. */\n  next(): void;\n  /** Go to the previous demo. */\n  prev(): void;\n  /** Current demo index. */\n  getIndex(): number;\n  /** Change the device viewport. */\n  setDevice(device: \"desktop\" | \"mobile\"): void;\n  /** Change the wrapper color scheme. */\n  setColorScheme(scheme: \"light\" | \"dark\"): void;\n  /** Override zoom level (null = auto-fit). */\n  setZoom(zoom: number | null): void;\n  /** Tear down listeners, observer, and DOM. */\n  destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEVICE_DIMENSIONS: Record<string, { w: number; h: number }> = {\n  desktop: { w: 1280, h: 800 },\n  mobile: { w: 390, h: 844 },\n};\n\nconst ZOOM_STEP = 0.1;\nconst ZOOM_MIN = 0.15;\nconst ZOOM_MAX = 1.5;\nconst STAGE_PADDING = 24;\nconst SHADOW_MARGIN = 40;\n\n// ---------------------------------------------------------------------------\n// Injected CSS (self-contained, prefixed persona-dc-)\n// ---------------------------------------------------------------------------\n\nconst CAROUSEL_CSS = /* css */ `\n/* ── Root ── */\n.persona-dc-root {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif;\n  font-size: 14px;\n  color: #111827;\n  line-height: 1.4;\n}\n\n/* ── Toolbar ── */\n.persona-dc-toolbar {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  padding: 8px 12px;\n  background: #fff;\n  border: 1px solid #e5e7eb;\n  border-bottom: none;\n  border-radius: 10px 10px 0 0;\n  flex-wrap: wrap;\n}\n.persona-dc-toolbar-lead {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  min-width: 0;\n}\n.persona-dc-toolbar-trail {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.persona-dc-title-btn {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  font-weight: 600;\n  font-size: 13px;\n  white-space: nowrap;\n  max-width: 240px;\n  background: none;\n  border: 1px solid transparent;\n  border-radius: 0.375rem;\n  padding: 4px 6px;\n  cursor: pointer;\n  color: inherit;\n  font-family: inherit;\n  transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.persona-dc-title-btn:hover {\n  background: #f3f4f6;\n  border-color: #e5e7eb;\n}\n.persona-dc-title-btn .persona-dc-title-text {\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.persona-dc-title-btn .persona-dc-title-chevron {\n  flex-shrink: 0;\n  transition: transform 0.15s ease;\n}\n.persona-dc-title-btn[aria-expanded=\"true\"] .persona-dc-title-chevron {\n  transform: rotate(180deg);\n}\n\n/* ── Title dropdown ── */\n.persona-dc-dropdown {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  margin-top: 4px;\n  min-width: 220px;\n  max-width: 320px;\n  background: #fff;\n  border: 1px solid #e5e7eb;\n  border-radius: 8px;\n  box-shadow: 0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);\n  padding: 4px;\n  z-index: 100;\n  max-height: 300px;\n  overflow-y: auto;\n}\n.persona-dc-root .persona-dc-dropdown button.persona-dc-dropdown-item {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  justify-content: flex-start;\n  width: 100%;\n  padding: 8px 10px;\n  border: none;\n  background: none;\n  border-radius: 6px;\n  cursor: pointer;\n  text-align: left;\n  font-family: inherit;\n  font-size: 13px;\n  font-weight: 500;\n  color: #111827;\n  transition: background-color 0.1s ease;\n}\n.persona-dc-root .persona-dc-dropdown button.persona-dc-dropdown-item:hover {\n  background: #f3f4f6;\n}\n.persona-dc-root .persona-dc-dropdown button.persona-dc-dropdown-item[aria-current=\"true\"] {\n  background: #f5f5f5;\n  color: #0f0f0f;\n}\n.persona-dc-root .persona-dc-dropdown-desc {\n  font-weight: 400;\n  font-size: 12px;\n  color: #6b7280;\n  margin-top: 1px;\n  text-align: left;\n}\n.persona-dc-root .persona-dc-dropdown button.persona-dc-dropdown-item[aria-current=\"true\"] .persona-dc-dropdown-desc {\n  color: #737373;\n}\n.persona-dc-counter {\n  font-size: 12px;\n  color: #6b7280;\n  white-space: nowrap;\n}\n.persona-dc-zoom-controls {\n  display: flex;\n  align-items: center;\n  gap: 2px;\n}\n.persona-dc-zoom-level {\n  font-size: 12px;\n  color: #6b7280;\n  min-width: 36px;\n  text-align: center;\n  user-select: none;\n}\n.persona-dc-separator {\n  width: 1px;\n  height: 20px;\n  background: #e5e7eb;\n  flex-shrink: 0;\n}\n\n/* ── Stage ── */\n.persona-dc-stage {\n  height: 550px;\n  min-height: 400px;\n  padding: ${STAGE_PADDING}px;\n  overflow: auto;\n  background: #f0f1f3;\n  background-image: radial-gradient(circle, #e0e1e5 1px, transparent 1px);\n  background-size: 24px 24px;\n  border: 1px solid #e5e7eb;\n  border-radius: 0 0 10px 10px;\n  display: flex;\n}\n\n/* ── Iframe wrapper ── */\n.persona-dc-iframe-wrapper {\n  position: relative;\n  overflow: hidden;\n  background: #fff;\n  border-radius: 10px;\n  box-shadow: 0 16px 64px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n  margin: auto;\n  flex-shrink: 0;\n  transition: border-radius 0.2s ease;\n}\n.persona-dc-iframe-wrapper[data-color-scheme=\"dark\"] {\n  background: #0f172a;\n}\n\n/* ── Iframe ── */\n.persona-dc-iframe {\n  border: none;\n  display: block;\n  background: #fff;\n  transform-origin: top left;\n}\n.persona-dc-iframe-wrapper[data-color-scheme=\"dark\"] .persona-dc-iframe {\n  background: #0f172a;\n}\n\n/* ── Button/toggle base styles (standalone, no widget.css dependency) ── */\n.persona-dc-root .persona-icon-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  padding: 0.25rem;\n  border-radius: 0.375rem;\n  border: 1px solid #e5e7eb;\n  background: #ffffff;\n  color: #111827;\n  cursor: pointer;\n  line-height: 1;\n  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n}\n.persona-dc-root .persona-icon-btn:hover {\n  background: #f3f4f6;\n}\n.persona-dc-root .persona-icon-btn:focus-visible {\n  outline: 2px solid #171717;\n  outline-offset: 2px;\n}\n.persona-dc-root .persona-icon-btn[aria-pressed=\"true\"] {\n  background: #f3f4f6;\n  border-color: #d1d5db;\n}\n.persona-dc-root .persona-toggle-group {\n  display: inline-flex;\n  gap: 0;\n}\n.persona-dc-root .persona-toggle-group > .persona-icon-btn {\n  border-radius: 0;\n}\n.persona-dc-root .persona-toggle-group > .persona-icon-btn:first-child {\n  border-top-left-radius: 0.375rem;\n  border-bottom-left-radius: 0.375rem;\n}\n.persona-dc-root .persona-toggle-group > .persona-icon-btn:last-child {\n  border-top-right-radius: 0.375rem;\n  border-bottom-right-radius: 0.375rem;\n}\n\n/* ── Responsive ── */\n@media (max-width: 640px) {\n  .persona-dc-toolbar {\n    gap: 8px;\n  }\n  .persona-dc-zoom-controls {\n    display: none;\n  }\n  .persona-dc-stage {\n    height: 400px;\n    min-height: 300px;\n  }\n}\n`;\n\nfunction injectStyles(): void {\n  if (document.querySelector(\"style[data-persona-dc-styles]\")) return;\n  const style = document.createElement(\"style\");\n  style.setAttribute(\"data-persona-dc-styles\", \"\");\n  style.textContent = CAROUSEL_CSS;\n  document.head.appendChild(style);\n}\n\n// ---------------------------------------------------------------------------\n// Scale helpers (ported from theme editor)\n// ---------------------------------------------------------------------------\n\nfunction computeFitScale(\n  stage: HTMLElement,\n  dims: { w: number; h: number },\n): number {\n  const availW = stage.clientWidth - STAGE_PADDING * 2 - SHADOW_MARGIN;\n  const availH = stage.clientHeight - STAGE_PADDING * 2 - SHADOW_MARGIN;\n  if (availW <= 0 || availH <= 0) return 1;\n  return Math.min(availW / dims.w, availH / dims.h, 1);\n}\n\nfunction applyScale(\n  wrapper: HTMLElement,\n  iframe: HTMLIFrameElement,\n  dims: { w: number; h: number },\n  scale: number,\n  device: string,\n): void {\n  wrapper.style.width = `${dims.w * scale}px`;\n  wrapper.style.height = `${dims.h * scale}px`;\n  wrapper.style.borderRadius =\n    device === \"mobile\" ? `${32 * scale}px` : \"10px\";\n\n  iframe.style.width = `${dims.w}px`;\n  iframe.style.height = `${dims.h}px`;\n  iframe.style.transformOrigin = \"top left\";\n  iframe.style.transform = `scale(${scale})`;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createDemoCarousel(\n  container: HTMLElement,\n  options: DemoCarouselOptions,\n): DemoCarouselHandle {\n  const {\n    items,\n    initialIndex = 0,\n    initialDevice = \"desktop\",\n    initialColorScheme = \"light\",\n    showZoomControls = true,\n    showDeviceToggle = true,\n    showColorSchemeToggle = true,\n    onChange,\n  } = options;\n\n  if (items.length === 0) {\n    throw new Error(\"createDemoCarousel: items array must not be empty\");\n  }\n\n  injectStyles();\n\n  // ── State ──\n  let currentIndex = Math.max(0, Math.min(initialIndex, items.length - 1));\n  let currentDevice = initialDevice;\n  let currentScheme = initialColorScheme;\n  let zoomOverride: number | null = null;\n  let lastAutoScale = 1;\n  let destroyed = false;\n\n  // ── DOM ──\n  const root = createElement(\"div\", \"persona-dc-root\");\n\n  // Toolbar\n  const toolbar = createElement(\"div\", \"persona-dc-toolbar\");\n  const toolbarLead = createElement(\"div\", \"persona-dc-toolbar-lead\");\n  const toolbarTrail = createElement(\"div\", \"persona-dc-toolbar-trail\");\n\n  // Prev / title / next / counter\n  const prevBtn = createIconButton({\n    icon: \"chevron-left\",\n    label: \"Previous demo\",\n    size: 14,\n    onClick: () => navigate(-1),\n  });\n\n  // Title button with dropdown\n  const titleWrap = createElement(\"div\");\n  titleWrap.style.position = \"relative\";\n\n  const titleBtn = createElement(\"button\", \"persona-dc-title-btn\");\n  titleBtn.type = \"button\";\n  titleBtn.setAttribute(\"aria-expanded\", \"false\");\n  titleBtn.setAttribute(\"aria-haspopup\", \"listbox\");\n  const titleText = createElement(\"span\", \"persona-dc-title-text\");\n  const titleChevron = createElement(\"span\", \"persona-dc-title-chevron\");\n  const chevronSvg = renderLucideIcon(\"chevron-down\", 12, \"currentColor\", 2);\n  if (chevronSvg) titleChevron.appendChild(chevronSvg);\n  titleBtn.append(titleText, titleChevron);\n\n  // Dropdown list\n  const dropdown = createElement(\"div\", \"persona-dc-dropdown\");\n  dropdown.setAttribute(\"role\", \"listbox\");\n  dropdown.style.display = \"none\";\n  let dropdownOpen = false;\n\n  function buildDropdownItems(): void {\n    dropdown.innerHTML = \"\";\n    for (let i = 0; i < items.length; i++) {\n      const item = items[i];\n      const btn = createElement(\"button\", \"persona-dc-dropdown-item\");\n      btn.type = \"button\";\n      btn.setAttribute(\"role\", \"option\");\n      btn.setAttribute(\"aria-current\", i === currentIndex ? \"true\" : \"false\");\n      const titleSpan = createElement(\"span\");\n      titleSpan.textContent = item.title;\n      btn.appendChild(titleSpan);\n      if (item.description) {\n        const desc = createElement(\"span\", \"persona-dc-dropdown-desc\");\n        desc.textContent = item.description;\n        btn.appendChild(desc);\n      }\n      btn.addEventListener(\"click\", () => {\n        closeDropdown();\n        goTo(i);\n      });\n      dropdown.appendChild(btn);\n    }\n  }\n\n  function toggleDropdown(): void {\n    dropdownOpen = !dropdownOpen;\n    dropdown.style.display = dropdownOpen ? \"\" : \"none\";\n    titleBtn.setAttribute(\"aria-expanded\", dropdownOpen ? \"true\" : \"false\");\n    if (dropdownOpen) buildDropdownItems();\n  }\n\n  function closeDropdown(): void {\n    if (!dropdownOpen) return;\n    dropdownOpen = false;\n    dropdown.style.display = \"none\";\n    titleBtn.setAttribute(\"aria-expanded\", \"false\");\n  }\n\n  titleBtn.addEventListener(\"click\", (e) => {\n    e.stopPropagation();\n    toggleDropdown();\n  });\n\n  // Close on outside click\n  const onDocClick = (): void => closeDropdown();\n  document.addEventListener(\"click\", onDocClick);\n\n  titleWrap.append(titleBtn, dropdown);\n\n  const nextBtn = createIconButton({\n    icon: \"chevron-right\",\n    label: \"Next demo\",\n    size: 14,\n    onClick: () => navigate(1),\n  });\n\n  const counterEl = createElement(\"span\", \"persona-dc-counter\");\n\n  toolbarLead.append(prevBtn, titleWrap, nextBtn, counterEl);\n\n  // Device toggle\n  let deviceToggle: ReturnType<typeof createToggleGroup> | null = null;\n  if (showDeviceToggle) {\n    deviceToggle = createToggleGroup({\n      items: [\n        { id: \"desktop\", icon: \"monitor\", label: \"Desktop\" },\n        { id: \"mobile\", icon: \"smartphone\", label: \"Mobile\" },\n      ],\n      selectedId: currentDevice,\n      onSelect: (id) => {\n        currentDevice = id as \"desktop\" | \"mobile\";\n        wrapper.dataset.device = currentDevice;\n        zoomOverride = null;\n        rescale();\n      },\n    });\n    toolbarTrail.appendChild(deviceToggle.element);\n  }\n\n  // Zoom controls\n  let zoomLevelEl: HTMLSpanElement | null = null;\n  if (showZoomControls) {\n    const zoomWrap = createElement(\"div\", \"persona-dc-zoom-controls\");\n    const zoomOut = createIconButton({\n      icon: \"minus\",\n      label: \"Zoom out\",\n      size: 14,\n      onClick: () => {\n        const current = zoomOverride ?? lastAutoScale;\n        zoomOverride = Math.max(ZOOM_MIN, current - ZOOM_STEP);\n        rescale();\n      },\n    });\n    zoomLevelEl = createElement(\"span\", \"persona-dc-zoom-level\");\n    zoomLevelEl.title = \"Reset to 100%\";\n    zoomLevelEl.style.cursor = \"pointer\";\n    zoomLevelEl.addEventListener(\"click\", () => {\n      zoomOverride = 1;\n      rescale();\n    });\n    const zoomIn = createIconButton({\n      icon: \"plus\",\n      label: \"Zoom in\",\n      size: 14,\n      onClick: () => {\n        const current = zoomOverride ?? lastAutoScale;\n        zoomOverride = Math.min(ZOOM_MAX, current + ZOOM_STEP);\n        rescale();\n      },\n    });\n    const zoomFit = createIconButton({\n      icon: \"maximize\",\n      label: \"Fit to view\",\n      size: 14,\n      onClick: () => {\n        zoomOverride = null;\n        rescale();\n      },\n    });\n    zoomWrap.append(zoomOut, zoomLevelEl, zoomIn, zoomFit);\n    toolbarTrail.appendChild(zoomWrap);\n  }\n\n  // Color scheme toggle\n  if (showColorSchemeToggle) {\n    const sep = createElement(\"div\", \"persona-dc-separator\");\n    toolbarTrail.appendChild(sep);\n    const schemeToggle = createToggleGroup({\n      items: [\n        { id: \"light\", icon: \"sun\", label: \"Light\" },\n        { id: \"dark\", icon: \"moon\", label: \"Dark\" },\n      ],\n      selectedId: currentScheme,\n      onSelect: (id) => {\n        currentScheme = id as \"light\" | \"dark\";\n        wrapper.dataset.colorScheme = currentScheme;\n        applySchemeToIframe();\n      },\n    });\n    toolbarTrail.appendChild(schemeToggle.element);\n  }\n\n  // Open in new tab\n  const sep2 = createElement(\"div\", \"persona-dc-separator\");\n  toolbarTrail.appendChild(sep2);\n  const openBtn = createIconButton({\n    icon: \"external-link\",\n    label: \"Open in new tab\",\n    size: 14,\n    onClick: () => {\n      window.open(items[currentIndex].url, \"_blank\");\n    },\n  });\n  toolbarTrail.appendChild(openBtn);\n\n  toolbar.append(toolbarLead, toolbarTrail);\n\n  // Stage + iframe\n  const stage = createElement(\"div\", \"persona-dc-stage\");\n  const wrapper = createElement(\"div\", \"persona-dc-iframe-wrapper\");\n  wrapper.dataset.device = currentDevice;\n  wrapper.dataset.colorScheme = currentScheme;\n\n  const iframe = createElement(\"iframe\", \"persona-dc-iframe\");\n  iframe.setAttribute(\"sandbox\", \"allow-scripts allow-same-origin allow-forms\");\n  iframe.setAttribute(\"loading\", \"lazy\");\n  iframe.title = items[currentIndex].title;\n\n  wrapper.appendChild(iframe);\n  stage.appendChild(wrapper);\n  root.append(toolbar, stage);\n  container.appendChild(root);\n\n  // ── Logic ──\n\n  function applySchemeToIframe(): void {\n    try {\n      const body = iframe.contentDocument?.body;\n      if (!body) return;\n      if (currentScheme === \"dark\") {\n        body.classList.add(\"theme-dark\");\n      } else {\n        body.classList.remove(\"theme-dark\");\n      }\n    } catch {\n      // Cross-origin iframe: silently ignore\n    }\n  }\n\n  // Re-apply scheme after iframe loads new content\n  iframe.addEventListener(\"load\", () => applySchemeToIframe());\n\n  function updateDisplay(): void {\n    const item = items[currentIndex];\n    titleText.textContent = item.title;\n    counterEl.textContent = `${currentIndex + 1} / ${items.length}`;\n    iframe.title = item.title;\n  }\n\n  function navigate(delta: number): void {\n    const next = ((currentIndex + delta) % items.length + items.length) % items.length;\n    goTo(next);\n  }\n\n  function goTo(index: number): void {\n    if (index < 0 || index >= items.length) return;\n    currentIndex = index;\n    iframe.src = items[currentIndex].url;\n    updateDisplay();\n    onChange?.(currentIndex, items[currentIndex]);\n  }\n\n  function rescale(): void {\n    if (destroyed) return;\n    const dims = DEVICE_DIMENSIONS[currentDevice] ?? DEVICE_DIMENSIONS.desktop;\n    lastAutoScale = computeFitScale(stage, dims);\n    const scale = Math.max(\n      ZOOM_MIN,\n      Math.min(ZOOM_MAX, zoomOverride ?? lastAutoScale),\n    );\n    applyScale(wrapper, iframe, dims, scale, currentDevice);\n    if (zoomLevelEl) {\n      zoomLevelEl.textContent = `${Math.round(scale * 100)}%`;\n    }\n  }\n\n  // ResizeObserver\n  const resizeObserver = new ResizeObserver(() => rescale());\n  resizeObserver.observe(stage);\n\n  // Initial render\n  updateDisplay();\n  iframe.src = items[currentIndex].url;\n  // Defer initial scale to next frame so stage has layout dimensions\n  requestAnimationFrame(() => rescale());\n\n  // ── Handle ──\n\n  function destroy(): void {\n    if (destroyed) return;\n    destroyed = true;\n    resizeObserver.disconnect();\n    document.removeEventListener(\"click\", onDocClick);\n    root.remove();\n  }\n\n  return {\n    element: root,\n    goTo,\n    next: () => navigate(1),\n    prev: () => navigate(-1),\n    getIndex: () => currentIndex,\n    setDevice(device: \"desktop\" | \"mobile\") {\n      currentDevice = device;\n      wrapper.dataset.device = device;\n      deviceToggle?.setSelected(device);\n      zoomOverride = null;\n      rescale();\n    },\n    setColorScheme(scheme: \"light\" | \"dark\") {\n      currentScheme = scheme;\n      wrapper.dataset.colorScheme = scheme;\n    },\n    setZoom(zoom: number | null) {\n      zoomOverride = zoom;\n      rescale();\n    },\n    destroy,\n  };\n}\n"],"mappings":"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KAAK,QAAQ,EACnBA,EAAM,KAAK,WAAW,EACtBA,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,sDAAsD,EACjEA,EAAM,KAAK,GAAG,EACdA,EAAM,KAAK,EAAE,EACbA,EAAM,KAAK,uBAAuB,EAClCA,EAAM,KAAK,0DAA0D,EACrEA,EAAM,KAAK,IAAI,EACfA,EAAM,KAAK,oCAAoC,EAC/CA,EAAM,KAAK,eAAe,EAC1BA,EAAM,KAAK,cAAc,EACzBA,EAAM,KAAK,mCAAmC,EAC9CA,EAAM,KAAK,yBAAyB,EACpCA,EAAM,KAAK,eAAe,EAC1BA,EAAM,KAAK,SAAS,EACpBA,EAAM,KAAK,MAAM,EAEVA,EAAM,KAAK;AAAA,CAAI,CACxB,CAEA,SAAS+B,GAA0BlC,EAAayB,EAAwC,CACtF,IAAMpC,EAAQoC,GAAS,MACjBtB,EAAkB,CACtB,4BACA,8DACA,GACA,qCACA,4CACA,WACA,qBACA,oCACA,6BACA,2BACA,0BACA,iCACA,qEACA,GACA,2CACA,iEACA,GACA,4BACA,0BACA,kBACA,qBACA,sBACA,uBACA,mBACA,IACA,GACA,yBACA,kCACA,gCACA,2CACA,sBACA,wBACA,uBACA,IACA,GACA,kEACA,gDACA,wBACA,qFACA,gDACA,wBACA,wCACA,OACA,GACA,wCACA,8DACA,iEACA,uDACA,SACA,8CACA,6DACA,gCACA,SACA,gDACA,2BACA,GACA,+BACA,0CACA,6GACA,yHACA,yCACA,GACA,2CACA,gBACA,kDACA,oCACA,4CACA,WACA,GACA,wFACA,2CACA,UACA,GACA,oCACA,UACA,QACA,GACA,kDACA,8CACA,kBACA,sCACA,GACA,aACA,4CACA,2CACA,6BACA,sCACA,kCACA,0CACA,OACA,KACA,GACA,yCACA,sBACA,uDACA,GACA,0BACA,wCACA,8DACA,0BACA,gBACA,yDACA,mCACA,wBACA,6DACA,YACA,UACA,mBACA,SACA,GACA,iCACA,kBAAkBqB,GAAmBC,CAAO,CAAC,KAC7C,iBACF,EAEA,OAAIzB,EAAO,QAAQG,EAAM,KAAK,oBAAoBH,EAAO,M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","names":["markdown_parsers_entry_exports","__export","DOMPurify","import_marked","import_dompurify","init_markdown_parsers_entry","__esmMin","AudioPlaybackManager","init_audio_playback_manager","__esmMin","sampleRate","options","prebufferMs","w","AudioCtx","ctx","pcmData","data","merged","float32","source","callback","held","samples","buffer","now","cbs","cb","idx","numSamples","view","i","int16","normalizeHost","host","describeError","res","data","RuntypeSpeechEngine","init_runtype_speech_engine","__esmMin","init_audio_playback_manager","opts","AudioPlaybackManager","player","request","callbacks","gen","url","reader","done","value","err","error","FallbackSpeechEngine","init_fallback_speech_engine","__esmMin","primary","fallback","options","request","callbacks","started","error","runtype_tts_entry_exports","__export","FallbackSpeechEngine","RuntypeSpeechEngine","init_runtype_tts_entry","__esmMin","init_runtype_speech_engine","init_fallback_speech_engine","session_reconnect_exports","__export","createReconnectController","host","attempt","waitResolve","waitTimer","listenersAttached","wake","resolve","wakeIfActive","status","handleVisibility","handleOnline","attachListeners","detachListeners","waitBackoff","ms","finalizeFailure","handle","changed","msg","content","runLoop","backoff","DEFAULT_BACKOFF_MS","maxAttempts","reconnectStream","before","controller","body","res","found","m","seedContent","init_session_reconnect","__esmMin","index_exports","__export","ASK_USER_QUESTION_CLIENT_TOOL","ASK_USER_QUESTION_PARAMETERS_SCHEMA","ASK_USER_QUESTION_TOOL_NAME","AgentWidgetClient","AgentWidgetSession","AttachmentManager","BrowserSpeechEngine","DEFAULT_COMPONENTS","DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH","DEFAULT_FLOATING_LAUNCHER_WIDTH","DEFAULT_PALETTE","DEFAULT_SEMANTIC","DEFAULT_WIDGET_CONFIG","PRESETS","PRESET_FULLSCREEN","PRESET_MINIMAL","PRESET_SHOP","ReadAloudController","SUGGEST_REPLIES_CLIENT_TOOL","SUGGEST_REPLIES_PARAMETERS_SCHEMA","SUGGEST_REPLIES_TOOL_NAME","THEME_ZONES","VERSION","WEBMCP_TOOL_PREFIX","WebMcpBridge","accessibilityPlugin","animationsPlugin","applyThemeVariables","attachHeaderToContainer","brandPlugin","buildComposer","buildDefaultHeader","buildHeader","buildHeaderWithLayout","buildMinimalHeader","builtInClientToolsForDispatch","collectEnrichedPageContext","componentRegistry","createActionManager","createAgentExperience","createAskUserQuestionBubble","createBestAvailableVoiceProvider","createBubbleWithLayout","createCSATFeedback","createComboButton","createComponentMiddleware","createComponentStreamParser","createDefaultSanitizer","createDemoCarousel","createDirectivePostprocessor","createDropdownMenu","createFlexibleJsonStreamParser","createIconButton","createImagePart","createJsonStreamParser","createLabelButton","createLocalStorageAdapter","createMarkdownProcessor","createMarkdownProcessorFromConfig","createMessageActions","createNPSFeedback","createPlainTextParser","createPlugin","createRegexJsonParser","createRovingTablist","createStandardBubble","createTextPart","createTheme","createThemeObserver","createToggleGroup","createTypingIndicator","createVoiceProvider","createWidgetHostLayout","createXmlParser","index_core_default","defaultActionHandlers","defaultJsonActionParser","defaultParseRules","detectColorScheme","directivePostprocessor","ensureAskUserQuestionSheet","escapeHtml","extractComponentDirectiveFromMessage","fileToImagePart","formatEnrichedContext","generateAssistantMessageId","generateCodeSnippet","generateMessageId","generateStableSelector","generateUserMessageId","getActiveTheme","getColorScheme","getDisplayText","getHeaderLayout","getImageParts","getPreset","hasComponentDirective","hasImages","headerLayouts","highContrastPlugin","initAgentWidget","isAskUserQuestionMessage","isComponentDirectiveType","isDockedMountMode","isSuggestRepliesMessage","isVoiceSupported","isWebMcpToolName","latestAgentSuggestions","listRegisteredStreamAnimations","loadMarkdownParsers","markdownPostprocessor","mergeWithDefaults","normalizeContent","onMarkdownParsersReady","parseAskUserQuestionPayload","parseSuggestRepliesPayload","pickBestVoice","pluginRegistry","reducedMotionPlugin","registerStreamAnimationPlugin","removeAskUserQuestionSheet","renderComponentDirective","renderLoadingIndicatorWithFallback","renderLucideIcon","resolveDockConfig","resolveSanitizer","resolveTokens","stripWebMcpPrefix","themeToCssVariables","unregisterStreamAnimationPlugin","validateImageFile","validateTheme","__toCommonJS","import_marked","import_dompurify","loader","moduleCache","loadPromise","readySubscribers","markParsersReady","mod","subs","cb","onMarkdownParsersReady","cb","moduleCache","readySubscribers","loadMarkdownParsers","provideMarkdownParsers","mod","markParsersReady","onLoadFailure","err","loadPromise","loader","getMarkdownParsersSync","provideMarkdownParsers","DOMPurify","convertRendererOverrides","overrides","createMarkdownProcessor","options","markedInstance","text","parsers","getMarkdownParsersSync","escapeHtml","Marked","opts","rendererOverrides","createMarkdownProcessorFromConfig","config","defaultMarkdownProcessor","markdownPostprocessor","escapeAttribute","value","makeToken","idx","directiveReplacer","source","placeholders","working","match","jsonText","parsed","token","_","type","createDirectivePostprocessor","markdownConfig","processor","withTokens","html","tokenRegex","replacement","directivePostprocessor","DEFAULT_PURIFY_CONFIG","SAFE_DATA_URI","createDefaultSanitizer","purifyInstance","html","parsers","getMarkdownParsersSync","escapeHtml","DOMPurify","_node","data","val","resolveSanitizer","option","DELIMITER_RE","hasPipe","line","splitCells","s","cell","buildRow","cells","buildDelimiter","cols","fitCells","stabilizeStreamingTables","markdown","lines","changed","i","header","delimiter","fullDelimiter","j","row","normalized","WEBMCP_TOOL_PREFIX","webMcpToolDisplayTitles","recordWebMcpToolDisplayTitles","infos","info","title","getWebMcpToolDisplayTitle","toolName","stripWebMcpPrefix","log","message","rest","polyfillLoader","computeClientToolsFingerprint","tools","parts","t","hashFingerprintContent","cyrb53","str","seed","h1","h2","i","ch","content","a","b","WebMcpBridge","config","handler","mc","infos","err","log","recordWebMcpToolDisplayTitles","pageOrigin","info","def","schema","parseSchema","wireToolName","args","signal","errorResult","present","bareName","stripWebMcpPrefix","message","candidate","displayTitle","getWebMcpToolDisplayTitle","gateInfo","controller","timedOut","timer","onAbort","raw","safeStringifyArgs","normalizeSerializedResult","polyfillLoader","core","defaultBrowserConfirmHandler","toolName","list","pattern","matchesGlob","name","WEBMCP_TOOL_PREFIX","isWebMcpToolName","escaped","parsed","safeStringify","argsPreview","previewArgs","prompt","json","value","resolveArtifactDisplayMode","feature","artifactType","display","buildArtifactRefRawContent","displayMode","props","RUNTYPE_AGENT_PREFIX","RUNTYPE_FLOW_PREFIX","detectRuntypeTypeId","id","resolveTarget","target","targetProviders","trimmed","colon","prefix","rest","detected","resolver","fallback","import_partial_json","createElement","tag","className","element","createElementInDocument","documentRef","createFragment","createNode","options","children","name","value","style","source","property","appendable","child","cx","parts","ASK_USER_QUESTION_TOOL_NAME","ASK_USER_QUESTION_MAX","SHEET_SENTINEL","DEFAULT_FREE_TEXT_LABEL_ROWS","DEFAULT_FREE_TEXT_LABEL_PILLS","DEFAULT_FREE_TEXT_PLACEHOLDER","DEFAULT_SUBMIT_LABEL","DEFAULT_NEXT_LABEL","DEFAULT_BACK_LABEL","DEFAULT_SUBMIT_ALL_LABEL","DEFAULT_SKIP_LABEL","DEFAULT_SKELETON_PILLS","ATTR_CURRENT_INDEX","ATTR_QUESTION_COUNT","ATTR_ANSWERS","ATTR_GROUPED","ATTR_LAYOUT","resolveLayout","feature","getLayout","sheet","truncateWarned","escapeAttrValue","value","isAskUserQuestionMessage","message","resolveFeature","config","parseAskUserQuestionPayload","toolCall","complete","chunks","text","parsed","parsePartialJson","promptsFromPayload","payload","all","firstPrompt","promptAt","index","applyStyleVars","root","s","buildAffordance","layout","multiSelect","wrap","createElement","check","badge","buildPill","option","btn","content","label","desc","aff","buildSkeletonPill","el","buildPillList","prompt","list","cleanOptions","o","i","defaultLabel","otherRow","input","freeBtn","buildFreeTextRow","row","submit","buildMultiSelectActions","buildNavRow","count","back","rightGroup","skip","next","isFinal","readAnswersFromSheet","raw","writeAnswersToSheet","answers","getCurrentIndex","setCurrentIndex","getQuestionCount","isGroupedSheet","restoreAnswersFromMessage","prompts","stored","result","p","q","v","restoreIndexFromMessage","buildStructuredAnswers","indexed","seen","applySelectionState","currentIndex","selected","pills","pill","on","realPillLabels","freeInput","syncNavState","hasAnswer","multi","labels","renderCurrentPage","grouped","stepInline","oldStepper","qText","pillList","fresh","oldFree","oldMulti","oldNav","buildSheet","toolCallId","initialAnswers","initialIndex","header","syncSheetFromMessage","newCount","createAskUserQuestionBubble","bubble","ensureAskUserQuestionSheet","overlay","existing","removeAskUserQuestionSheet","selector","duration","getSelectedLabels","setCurrentAnswer","answer","idx","navigateToPage","clamped","SUGGEST_REPLIES_TOOL_NAME","SUGGEST_REPLIES_PARAMETERS_SCHEMA","SUGGEST_REPLIES_CLIENT_TOOL","SUGGEST_REPLIES_TOOL_NAME","suggestRepliesToolResult","isSuggestRepliesMessage","message","parseSuggestRepliesPayload","args","parsed","raw","chips","item","latestAgentSuggestions","messages","i","shouldExposeSuggestReplies","config","feature","ASK_USER_QUESTION_PARAMETERS_SCHEMA","ASK_USER_QUESTION_MAX","ASK_USER_QUESTION_CLIENT_TOOL","ASK_USER_QUESTION_TOOL_NAME","builtInClientToolsForDispatch","config","tools","ask","shouldExposeSuggestReplies","SUGGEST_REPLIES_CLIENT_TOOL","import_partial_json","unescapeJsonString","str","formatUnknownValue","value","formatReasoningDuration","reasoning","end","start","seconds","describeReasonStatus","formatToolDuration","tool","describeToolTitle","tool","formatToolDuration","formatElapsedMs","ms","seconds","computeToolElapsed","durationMs","computeReasoningElapsed","reasoning","resolveToolHeaderText","template","fallback","toolName","duration","parseFormattedTemplate","resolved","segments","regex","lastIndex","match","pushSegments","text","styles","parts","i","createRegexJsonParserInternal","extractedText","processedLength","extractTextFromIncompleteJson","jsonString","textFieldRegex","incompleteTextFieldRegex","incompleteMatch","accumulatedContent","trimmed","extracted","extractTextFromJson","parsed","createPlainTextParser","parser","_accumulatedContent","createRegexJsonParser","regexParser","createJsonStreamParser","parsePartialJson","unescapeJsonString","createFlexibleJsonStreamParser","textExtractor","extractText","getText","value","newText","createXmlParser","version","VERSION","version","DEFAULT_ENDPOINT","DEFAULT_CLIENT_API_BASE","filenameFromMediaType","mediaType","lower","ext","slash","subtype","hasValidContent","message","getParserFromType","parserType","createJsonStreamParser","createRegexJsonParser","createXmlParser","createPlainTextParser","looksStructured","value","preferFinalStructuredContent","rawBuffer","finalString","rawTrimmed","finalTrimmed","rawLooksStructured","rawJsonText","extractTextFromJson","AgentWidgetClient","config","VERSION","WebMcpBridge","next","callback","handler","wireToolName","args","signal","agentId","flowId","target","targetProviders","resolved","resolveTarget","endpoint","session","storedSessionId","routed","sessionTargetId","requestBody","response","error","data","feedback","errorData","messageId","type","rating","comment","options","onEvent","basePayload","sanitizedMetadata","key","baseChatRequest","m","commitClientToolsFingerprint","toolFields","chatRequest","err","payload","headers","dynamicHeaders","body","assistantMessageId","seedContent","approval","decision","url","sessionId","fullClientTools","doFetch","opts","hasClientTools","clientToolsFingerprint","computeClientToolsFingerprint","sameSession","unchanged","sendEmptyReplace","forceFull","attempt","executionId","toolOutputs","isClientToken","resumeSessionId","builtInClientToolsForDispatch","commit","resumeRequest","messages","routedAgentId","normalizedMessages","b","timeA","timeB","clientTools","contextAggregate","provider","result","assistantMessageRef","emitMessage","nextSequence","partIdState","createNewAssistant","partId","msg","ensureAssistant","assistant","reader","decoder","buffer","baseSequence","sequenceCounter","cloneMessage","reasoning","toolCall","tools","tool","shouldEmitMessage","hasContentParts","hasRawContent","assistantMessage","lastAssistantInTurn","customParsePartId","currentTextBlockId","pendingFlowRaw","nestedBlockParent","nestedBlockMessages","nestedBlockRaw","reasoningMessages","toolMessages","reasoningContext","toolContext","normalizeKey","getStepKey","getToolCallKey","baseAssistantId","assistantIdConsumed","ensureAssistantMessage","id","initialContent","segment","trackReasoningId","stepKey","resolveReasoningId","allowCreate","rawId","existing","generated","ensureReasoningMessage","reasoningId","trackToolId","callKey","artifactToolCallIds","artifactCardMessages","artifactIdsWithCards","artifactContent","isArtifactEmitToolName","name","normalized","resolveToolId","ensureToolMessage","toolId","resolveTimestamp","parsed","dateParsed","ensureStringContent","streamParsers","rawContentBuffers","orderedChunkBuffers","insertOrderedChunk","seq","text","chunks","lo","hi","mid","accumulated","index","reconcileSealedAssistantWithFinalResponse","finalContent","contentToProcess","parser","mergeIfBetter","mergedDisplay","cur","finalizeCleanup","closeResult","extractedFromJson","bestDisplayText","extracted","parsedResult","lastSealedFlowBubble","applyFlowTextChunk","accumulatedRaw","chunk","chunkSeq","looksLikeJson","finalizeFlowTextBlock","effectiveFinal","extractedText","asyncPending","parserToClose","ensureNestedBlockMessage","blockId","parentToolCallId","variant","seqReadyQueue","drainReadyQueue","mediaBuffers","lastIterationSeen","executionKind","executionKindResolved","openTurnId","agentExecution","agentIterationMessages","iterationDisplay","i","payloadType","rStartBlockId","rStartParent","reasoningMessage","rDeltaBlockId","nested","nestedChunk","reasonSeq","ordered","start","rCompleteBlockId","nestedReflection","reflectionText","toolName","toolMessage","chunkText","agentCtxChunk","durationValue","agentCtxComplete","toolCallId","rawToolName","isWebMcpToolName","webMcpTool","startBlockId","startParent","prev","deltaBlockId","nestedParent","nestedDelta","nestedRaw","delta","completeBlockId","stepType","executionType","e","finalMsg","sealed","flowStopReason","finalResponse","hasResponse","iteration","prevMsg","turnStopReason","stopReasonTarget","turnId","buf","mediaBlockId","completeMediaType","completeData","completeUrl","reconstructed","mediaToolCallId","rawMedia","mediaContentParts","part","rec","partType","rawMediaType","src","resolvedMediaType","toolCallIdRaw","mediaMessage","prevAssistant","kind","errorMessage","approvalId","approvalMessage","existingMessage","at","artId","artTitle","rawFile","artFile","startProps","displayMode","resolveArtifactDisplayMode","artComponent","cardMsg","buildArtifactRefRawContent","deltaId","deltaText","acc","props","updateAcc","artCompleteId","refMsg","roleRaw","refArtId","resolvedError","done","events","event","lines","eventType","frameId","line","advanceCursor","handled","generateMessageId","timestamp","random","generateUserMessageId","generateAssistantMessageId","IMAGE_ONLY_MESSAGE_FALLBACK_TEXT","normalizeContent","content","getDisplayText","part","hasImages","getImageParts","createTextPart","text","createImagePart","image","options","fileToImagePart","file","resolve","reject","reader","dataUri","validateImageFile","acceptedTypes","maxSizeBytes","IMAGE_MIME_TYPES","DOCUMENT_MIME_TYPES","ALL_SUPPORTED_MIME_TYPES","isImageMimeType","mimeType","isImageFile","fileToContentPart","file","resolve","reject","reader","dataUri","isImageFile","validateFile","acceptedTypes","ALL_SUPPORTED_MIME_TYPES","maxSizeBytes","getFileExtension","filename","parts","getFileTypeName","mimeType","ext","init_audio_playback_manager","CAPTURE_SAMPLE_RATE","PLAYBACK_SAMPLE_RATE","CAPTURE_BUFFER_SIZE","RIFF_MAGIC","stripWavHeader","buf","toWsBase","host","trimmed","RuntypeVoiceProvider","config","agentId","token","generation","stream","t","AudioCtx","captureContext","engine","AudioPlaybackManager","wsUrl","ws","event","evt","codeMsg","error","context","source","processor","e","input","pcm16","i","s","msg","role","pcm","callback","status","cb","text","isFinal","metrics","BrowserVoiceProvider","config","cb","SpeechRecognition","event","transcript","result","isFinal","error","callback","createVoiceProvider","config","RuntypeVoiceProvider","BrowserVoiceProvider","custom","provider","createBestAvailableVoiceProvider","isVoiceSupported","pickBestVoice","voices","preferred","name","match","v","BrowserSpeechEngine","_BrowserSpeechEngine","options","request","callbacks","synth","utterance","event","reason","ReadAloudController","resolveEngine","id","listener","request","generation","resolved","state","resolveSpeakableText","raw","actionText","extractActionMessageText","stripMarkdownForSpeech","body","fence","parsed","markdown","text","loader","loadRuntypeTts","loader","CONNECTION_CONFIG_KEYS","connectionConfigChanged","prev","next","key","buildDispatchErrorContent","error","override","err","base","buildWebMcpErrorResult","message","getWebMcpErrorMessage","fallback","isAutoResolvedLocalToolName","name","isWebMcpToolName","SUGGEST_REPLIES_TOOL_NAME","AgentWidgetSession","config","callbacks","ReadAloudController","event","tc","autoResolvable","webMcpPending","AgentWidgetClient","rec","tts","host","agentId","loadRuntypeTts","callback","isVoiceSupported","browser","BrowserSpeechEngine","clientToken","wantFallback","RuntypeSpeechEngine","FallbackSpeechEngine","runtype","voiceConfig","createVoiceProvider","processingErrorText","result","role","text","isFinal","msg","assistantMsg","metrics","status","providerConfig","session","welcomeMessage","messageId","type","rating","comment","merged","prevSSECallback","options","content","llmContent","contentParts","id","createdAt","sequence","streaming","voiceProcessing","rawContent","generateUserMessageId","generateAssistantMessageId","optionsList","results","component","props","directive","rawInput","input","userMessageId","assistantMessageId","userMessage","IMAGE_ONLY_MESSAGE_FALLBACK_TEXT","controller","snapshot","isAbortError","stream","preserveId","hasStale","webmcp","info","approval","approvalMessageId","resolve","decision","existing","m","updatedApproval","updatedMessage","approvalConfig","onDecision","response","errorData","toolMessage","progress","current","answers","answer","executionId","toolName","structuredAnswers","args","questions","qText","toolCallId","q","v","stored","p","i","ans","answerStr","callId","queuedEpoch","batch","snapshots","candidates","candidate","startedAt","completedAt","extraMetadata","claimedKeys","controllers","resumeController","executed","wireToolName","dedupeKey","resumeKey","suggestRepliesToolResult","execPromise","output","ready","r","toolOutputs","malformedKey","resolveController","signal","isSuggestReplies","phase","resumeOutput","manual","refId","displayMode","resolveArtifactDisplayMode","buildArtifactRefRawContent","ev","row","messages","artifacts","selectedId","isNew","createReconnectController","value","body","seedContent","resume","assistantId","had","immediate","cb","wasStreaming","ttsConfig","lastAssistant","resolveSpeakableText","voices","pickBestVoice","listener","withSequence","index","idx","prior","incoming","reTcName","reExecId","reTcId","reKey","isInflight","isResolved","existingToolName","hasCompletedTool","a","b","timeA","timeB","seqA","seqB","import_lucide","LUCIDE_ICONS","FileIcon","ImageIcon","renderLucideIcon","iconName","size","color","strokeWidth","iconData","createSvgFromIconData","svg","elementData","tagName","attrs","element","key","value","DEFAULTS","ALL_SUPPORTED_MIME_TYPES","generateAttachmentId","getFileIconName","mimeType","AttachmentManager","_AttachmentManager","config","container","a","files","file","validation","validateFile","reason","contentPart","fileToContentPart","previewUrl","isImageFile","attachment","error","id","index","previewEl","isImage","previewWrapper","createElement","img","filePreview","iconName","fileIcon","renderLucideIcon","typeLabel","getFileTypeName","removeBtn","xIcon","e","onAttachmentsChange","isObject","value","deepMerge","base","override","merged","key","existing","DEFAULT_FLOATING_LAUNCHER_WIDTH","DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH","DEFAULT_LAUNCHER_CONFIG","DEFAULT_WIDGET_CONFIG","mergeThemePartials","base","override","deepMerge","mergeWithDefaults","config","da","ca","dsb","csb","dsc","csc","dsa","csa","dau","cau","mergedArtifacts","mergedScrollToBottom","mergedScrollBehavior","mergedStreamAnimation","mergedAskUserQuestion","DEFAULT_PANEL_INSET","DEFAULT_PANEL_CANVAS_BACKGROUND","DEFAULT_PALETTE","DEFAULT_SEMANTIC","DEFAULT_COMPONENTS","DEFAULT_FLOATING_LAUNCHER_WIDTH","DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH","resolveTokenValue","theme","path","parts","current","part","resolveTokens","resolved","resolveObject","obj","prefix","key","value","resolvedValue","validateTheme","errors","warnings","mergeRecords","base","override","result","existing","deepMergeComponents","createTheme","userConfig","options","baseTheme","validation","e","plugin","themeToCssVariables","cssVars","token","varName","headerTokens","introCardTokens","mdH1Size","mdH1Weight","mdH2Size","mdH2Weight","mdProseFont","components","iconBtn","labelBtn","toggleGrp","artifact","t","toolbarBg","code","stateSurface","stateContainer","gray100","gray200","gray300","flatTheme","hoverBgDefault","activeBgDefault","THEME_ZONES","DARK_PALETTE","normalizeThemeConfig","theme","detectColorScheme","getColorSchemeFromConfig","config","colorScheme","getColorScheme","createLightTheme","userConfig","createTheme","createDarkTheme","baseTheme","getActiveTheme","scheme","lightThemeConfig","darkThemeConfig","deepMerge","getCssVariables","themeToCssVariables","applyThemeVariables","element","cssVars","name","value","createThemeObserver","callback","cleanupFns","observer","mediaQuery","handleChange","fn","basenameOf","path","parts","extensionOf","base","dot","extractFileSource","markdown","firstNl","rest","lastNl","fileKindOf","meta","ext","mime","fileTypeLabel","downloadInfoFor","record","raw","SVG_NS","createSpinner","className","svg","cx","track","arc","appendCharSpans","container","text","startIndex","idx","char","span","createElement","applyArtifactLoadingStatus","statusText","text","artifactsCfg","loadingAnimation","duration","appendCharSpans","STATUS_LABEL_CLASS","STATUS_DETAIL_CLASS","APPLIED_LABEL_ATTR","firstSeenAt","now","clearArtifactStatusTracking","id","typeLabelFor","record","file","fileTypeLabel","resolveArtifactStatusLabel","cfg","surface","typeLabel","defaultLabel","statusLabel","firstSeen","elapsedMs","isComponent","markdown","chars","lines","ctx","result","resetLabelSpan","labelEl","applyArtifactStatus","container","resolved","createElement","applyArtifactLoadingStatus","detailEl","detail","TOKEN_CLASS","MAX_HIGHLIGHT_LENGTH","LANGUAGE_ALIASES","resolveHighlightLanguage","language","path","lang","dot","ext","runRules","source","rules","tokens","i","plainStart","flushPlain","end","matched","rule","m","type","JS_KEYWORDS","JS_RULES","t","tokenizeJs","JSON_RULES","tokenizeJson","idx","j","next","CSS_RULES","tokenizeCss","lastSignificant","tok","trimmed","HTML_TAG_RULES","tokenizeHtml","push","value","n","stop","tagEnd","tagText","embed","tag","cm","innerEnd","inner","innerTokens","lt","tokenizeByLanguage","buildLines","frag","createFragment","line","createElement","appendSegment","text","cls","span","parts","p","highlightCode","statusCopy","DEFAULT_OVERLAY_Z_INDEX","PORTALED_OVERLAY_Z_INDEX","createDropdownMenu","options","items","onSelect","anchor","position","portal","menu","createElement","PORTALED_OVERLAY_Z_INDEX","item","hr","btn","icon","renderLucideIcon","labelSpan","e","hide","cleanupClickOutside","reposition","rect","show","handler","toggle","destroy","createIconButton","options","icon","label","size","strokeWidth","className","onClick","aria","btn","createElement","svg","renderLucideIcon","key","value","createLabelButton","variant","iconSize","classString","span","createToggleGroup","items","selectedId","onSelect","wrapper","currentId","buttons","updatePressed","entry","item","setSelected","id","createComboButton","menuItems","position","portal","hover","labelEl","chevron","dropdown","createDropdownMenu","e","isOpen","text","CUSTOM_ACTION_CLASS","buildArtifactActionButton","action","opts","documentChrome","btn","className","createElement","el","span","createLabelButton","createIconButton","artifactRecordActionContext","record","ctx","renderDefaultArtifactCard","props","context","file","rawTitle","title","basenameOf","artifactId","status","artifactType","subtitle","fileTypeLabel","root","iconBox","meta","titleEl","subtitleEl","artifactsCfg","record","resolved","resolveArtifactStatusLabel","applyArtifactStatus","clearArtifactStatusTracking","cardActions","ctx","action","btn","buildArtifactActionButton","dl","createLabelButton","PersonaArtifactCard","customRenderer","result","inlineBlockUpdaters","updateInlineArtifactBlocks","root","artifacts","opts","byId","a","el","update","record","hasLiveInlineArtifactBlock","recordFromProps","props","id","title","status","embedded","componentProps","file","chromeLabelsFor","basenameOf","typeLabel","fileTypeLabel","cardPropsFromRecord","COLLAPSE_FADE_OUT_MS","COLLAPSE_HEIGHT_BASE_MS","COLLAPSE_HEIGHT_MS_PER_PX","COLLAPSE_HEIGHT_MIN_MS","COLLAPSE_HEIGHT_MAX_MS","COLLAPSE_HEIGHT_EASING","COLLAPSE_CARD_FADE_IN_MS","COLLAPSE_CARD_FADE_DELAY_FRACTION","animateInlineCollapse","startHeight","swap","onSettled","reduceMotion","fadeOuts","child","anim","clearRoot","endHeight","distance","duration","cardFadeDelay","card","cardFade","heightAnim","done","clear","resolveInlineChrome","cfg","inlineChrome","BODY_HEIGHT_VAR","resolveInlineBody","b","streamingView","viewMode","streamingHeight","completeHeight","h","overflow","followOutput","fadeTop","fadeBottom","fm","transition","completeDisplay","renderDefaultArtifactInline","context","artifactsCfg","chromeEnabled","showCopy","showExpand","showViewToggle","inlineActions","bodyLayout","collapseToCard","userViewMode","currentRecord","createElement","renderCardChild","rec","PersonaArtifactCard","mountCard","r","handle","renderArtifactPreviewBody","body","applyBodyHeight","streaming","numeric","hasCodePre","fillCapable","lastBodyStatus","runBodyUpdate","runArtifactBodyTransition","clipExpandHitbox","chrome","lead","iconWrap","icon","renderLucideIcon","titleEl","metaEl","actions","customActionsWrap","effectiveViewMode","viewToggle","createToggleGroup","next","copyBtn","createIconButton","expandBtn","updateChrome","resolved","resolveArtifactStatusLabel","applyArtifactStatus","clearArtifactStatusTracking","canToggle","kind","fileKindOf","ctx","artifactRecordActionContext","action","btn","buildArtifactActionButton","latest","dirtySinceSwap","buffer","PersonaArtifactInline","customRenderer","artifactId","artifactType","result","ComponentRegistry","name","renderer","options","components","componentRegistry","PersonaArtifactCard","PersonaArtifactInline","artifactCopyText","record","raw","extractFileSource","bodyTransitionInFlight","sanitizeViewTransitionName","id","runArtifactBodyTransition","target","transition","swap","start","reduceMotion","name","done","PRE_CLASS","MARKDOWN_WRAP_CLASS","fallbackComponentCard","card","createElement","title","pre","resolvePreviewLoading","loading","o","PREVIEW_FADE_MS","makePreviewToken","a","buildReadyReporter","token","buildPreviewOverlay","artifactId","config","opts","overlay","custom","indicator","createSpinner","labelEl","setupPreviewLoading","frame","iframe","shownAt","settled","timers","clearTimers","addTimer","fn","ms","removeOverlay","showOverlay","built","fadeOut","detachListeners","onMessage","onLoad","settle","remaining","e","d","raf","cb","renderArtifactPreviewBody","ctx","registry","componentRegistry","layout","md","createMarkdownProcessorFromConfig","sanitize","resolveSanitizer","current","parserRerenderScheduled","toHtml","text","parsersReady","getMarkdownParsersSync","onMarkdownParsersReady","render","escapeHtml","el","filePreviewFrame","filePreviewKey","filePreviewLoadingCleanup","resetFilePreview","sourceRoot","sourceCode","sourceKey","escaped","resetSource","statusView","statusKey","resetStatus","NEAR_BOTTOM_PX","followRaf","scheduleFrame","isNearBottom","sc","updateFadeClasses","overflow","distToBottom","stickToBottom","renderSource","rec","fixed","useWindow","key","code","scroll","overflows","wasNearBottom","highlightCode","streaming","renderMarkdown","wrap","renderStatusView","artifactsCfg","resolved","resolveArtifactStatusLabel","textEl","applyArtifactStatus","viewMode","fileMeta","isStreaming","clearArtifactStatusTracking","stateHeight","source","kind","fileKindOf","previewEnabled","fp","sandbox","loadingOpts","renderer","componentCtx","node","import_idiomorph","morphMessages","container","newContent","options","preserveTypingAnimation","oldNode","newNode","oldText","newText","normalizeCopiedSelectionText","text","INITIAL_HISTORY_STATE","navigateComposerHistory","input","direction","history","currentValue","atStart","state","inHistory","index","computeMessageFingerprint","message","configVersion","createMessageCache","getCachedWrapper","cache","messageId","fingerprint","entry","setCachedWrapper","wrapper","pruneCache","activeMessageIds","key","createFollowStateController","initiallyFollowing","following","getScrollBottomOffset","element","isElementNearBottom","threshold","resolveFollowStateFromScroll","input","currentScrollTop","lastScrollTop","nearBottom","userScrollThreshold","isAutoScrolling","pauseOnUpwardScroll","pauseWhenAwayFromBottom","resumeRequiresDownwardScroll","delta","resolveFollowStateFromWheel","deltaY","resumeWhenNearBottom","hasSelectionWithin","selection","container","computeAnchorScrollState","targetScrollTop","spacerHeight","computeShrunkSpacerHeight","growth","DEFAULT_STREAM_ANIMATION","DEFAULT_SKIP_TAGS","resolveStreamAnimation","feature","BUILTIN_PLUGINS","globalRegistry","plugin","registerStreamAnimationPlugin","unregisterStreamAnimationPlugin","name","p","listRegisteredStreamAnimations","resolveStreamAnimationPlugin","type","overrides","applyStreamBuffer","content","buffer","message","streaming","lastSpace","lastNewline","makeCharSpan","doc","ch","messageId","index","span","makeWordSpan","word","WHITESPACE_RE","shouldSkipSubtree","node","skipTags","current","el","wrapTextNodeChars","textNode","counterRef","parent","text","fragment","j","group","wrapTextNodeWords","tokens","token","wrapStreamAnimation","html","mode","options","scratch","t","walker","textNodes","wrap","createStreamCaret","caret","createSkeletonPlaceholder","wrapper","line","injectedStyleRoots","injectPluginStyles","root","names","escaped","style","attachedCleanups","attachPlugin","cleanups","cleanup","detachAllPlugins","ensurePluginActive","syncOverlayHostStacking","host","zIndex","DEFAULT_OVERLAY_Z_INDEX","originalPosition","originalZIndex","originalIsolation","computed","positionWasSet","lockCount","savedState","acquireScrollLock","doc","body","scrollY","released","win","DEFAULT_DOCK_CONFIG","isDockedMountMode","config","isComposerBarMountMode","resolveDockConfig","dock","positionMap","DEFAULT_WRAPPER_CLASS","createCloseButton","config","options","showClose","wrapperClassName","buttonSize","iconSize","launcher","closeButtonSize","wrapper","createElement","closeButtonTooltipText","closeButtonShowTooltip","closeButtonIconName","closeButtonIconText","hasCloseBorder","button","createNode","cx","HEADER_THEME_CSS","closeIconSvg","renderLucideIcon","portaledTooltip","showTooltip","tooltipDocument","tooltipContainer","createElementInDocument","arrow","buttonRect","PORTALED_OVERLAY_Z_INDEX","hideTooltip","DEFAULT_CLEAR_CHAT_WRAPPER_CLASS","createClearChatButton","clearChatConfig","clearChatSize","clearChatIconName","clearChatIconColor","clearChatBgColor","clearChatBorderWidth","clearChatBorderColor","clearChatBorderRadius","clearChatPaddingX","clearChatPaddingY","clearChatTooltipText","clearChatShowTooltip","hasClearChatBorder","iconSvg","HEADER_THEME_CSS","buildHeader","context","config","showClose","header","createNode","launcher","headerIconSize","closeButtonPlacement","headerIconHidden","headerIconName","iconHolder","iconSize","iconSvg","renderLucideIcon","img","createElement","headerCopy","title","subtitle","clearChatConfig","clearChatEnabled","clearChatPlacement","clearChatButton","clearChatButtonWrapper","parts","createClearChatButton","closeButtonWrapperClass","closeButton","closeButtonWrapper","createCloseButton","attachHeaderToContainer","container","headerElements","buildDefaultHeader","context","elements","buildHeader","onTitleClick","headerCopy","e","appendTrailingHeaderActions","container","actions","onAction","a","btn","createElement","ic","renderLucideIcon","wrapper","dropdown","createDropdownMenu","itemId","buildMinimalHeader","config","showClose","onClose","layoutHeaderConfig","onHeaderAction","launcher","header","titleMenuConfig","titleRow","headerTitle","createComboButton","HEADER_THEME_CSS","handleTitleClick","hoverCfg","closeButtonSize","closeButtonWrapper","closeButton","closeButtonIconName","closeIconSvg","iconHolder","headerSubtitle","headerLayouts","getHeaderLayout","layoutName","buildHeaderWithLayout","layoutConfig","customHeader","headerElements","createComposerTextarea","config","textarea","createElement","defaultMaxLines","lineHeight","readMaxHeight","parsed","attachAutoResize","newHeight","createSendButton","sendButtonConfig","useIcon","iconText","iconName","stopIconName","tooltipText","stopTooltipText","sendLabel","stopLabel","showTooltip","buttonSize","backgroundColor","textColor","wrapper","button","createNode","cx","sendIcon","stopIcon","iconSize","iconColor","renderLucideIcon","tooltip","currentMode","mode","label","next","createMicButton","voiceRecognitionConfig","hasSpeechRecognition","hasRuntypeProvider","micIconName","micIconSize","micIconSizeNum","micBackgroundColor","micIconColor","micIconSvg","micTooltipText","createAttachmentControls","attachmentsConfig","previewsContainer","input","ALL_SUPPORTED_MIME_TYPES","attachIconName","attachIconSize","buttonSizeNum","attachIconSizeNum","attachIconSvg","e","attachTooltipText","createStatusText","statusConfig","alignClass","statusText","isVisible","idleLabel","link","createSuggestionsRow","buildComposer","context","config","footer","createNode","suggestions","createSuggestionsRow","composerForm","textarea","attachAutoResize","createComposerTextarea","send","createSendButton","mic","createMicButton","attachment","createAttachmentControls","statusText","createStatusText","actionsRow","leftActions","createElement","rightActions","e","buildPillPeekBanner","root","createNode","iconHolder","createElement","messageIcon","renderLucideIcon","textNode","caret","caretIcon","buildPillComposer","context","config","footer","suggestions","createSuggestionsRow","statusText","createStatusText","textarea","attachAutoResize","createComposerTextarea","send","createSendButton","mic","createMicButton","attachment","createAttachmentControls","composerForm","leftActions","rightActions","e","actionsRow","createWrapper","config","launcherEnabled","dockedMode","isDockedMountMode","isComposerBarMountMode","cb","wrapper","createElement","DEFAULT_OVERLAY_Z_INDEX","panel","pillRoot","inlineWidth","launcher","position","positionMap","width","DEFAULT_FLOATING_LAUNCHER_WIDTH","buildComposerBarPanel","showClose","container","closeButton","closeButtonWrapper","createCloseButton","clearChatEnabled","clearChatButton","clearChatButtonWrapper","parts","createClearChatButton","headerPlaceholder","createNode","body","introTitle","introSubtitle","introCard","messagesWrapper","contentMaxWidth","composerOverlay","composerElements","buildPillComposer","peekBanner","peekTextNode","buildPillPeekBanner","buildPanel","headerLayoutConfig","showHeader","headerElements","buildHeaderWithLayout","buildHeader","buildComposer","showFooter","attachHeaderToContainer","createLauncherButton","config","onToggle","button","createElement","update","newConfig","launcher","dockedMode","isDockedMountMode","titleEl","t","subtitleEl","s","textContainer","icon","iconSize","iconSizeNum","iconSvg","renderLucideIcon","img","callToActionIconEl","callToActionIconSize","paddingTotal","containerSize","positionClass","positionMap","floatingBase","dockedBase","DEFAULT_OVERLAY_Z_INDEX","defaultBorder","defaultShadow","destroy","createWidgetView","config","showClose","wrapper","panel","pillRoot","createWrapper","panelElements","buildPanel","shell","transcript","header","composer","next","nextFooter","resolveLauncher","plugins","onToggle","launcherPlugin","p","customLauncher","createLauncherButton","instance","getDefaultStopReasonNoticeCopy","stopReason","resolveStopReasonNoticeText","overrides","fallback","override","text","createStopReasonNotice","notice","createElement","isSafeImageSrc","src","lower","isSafeMediaSrc","MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX","MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX","getMessageImageParts","message","part","getMessageAudioParts","getMessageVideoParts","getMessageFileParts","createMessageImagePreviews","imageParts","hasVisibleText","onPreviewFailed","container","visiblePreviewCount","failureHandled","handleTotalPreviewFailure","imagePart","index","imageElement","settled","createMessageAudioPreviews","audioParts","visible","audioElement","createMessageVideoPreviews","videoParts","videoElement","createMessageFilePreviews","fileParts","link","createTypingIndicator","dot1","dot2","dot3","srOnly","renderLoadingIndicatorWithFallback","location","customRenderer","widgetConfig","context","result","createAvatar","avatarConfig","role","avatar","avatarContent","img","createTimestamp","timestampConfig","tagName","timestamp","date","getBubbleClasses","layout","baseClasses","createMessageActions","actionsConfig","_callbacks","showCopy","showUpvote","showDownvote","showReadAloud","empty","visibility","align","alignClass","layoutClass","createActionButton","iconName","label","dataAction","button","createIconButton","createStandardBubble","transform","layoutConfig","actionCallbacks","options","config","showAvatar","showTimestamp","avatarPosition","timestampPosition","classes","bubble","messageContentText","shouldHideTextUntilPreviewFails","IMAGE_ONLY_MESSAGE_FALLBACK_TEXT","streamAnimation","resolveStreamAnimation","streamPluginOverrides","streamPlugin","resolveStreamAnimationPlugin","pluginStillAnimating","streamAnimationActive","contentDiv","bufferedContent","applyStreamBuffer","transformedContent","animatedContent","wrapStreamAnimation","textContentDiv","caret","createStreamCaret","spans","lastSpan","lastChild","lastBlock","imagePreviews","audioPreviews","videoPreviews","filePreviews","stopReasonNoticeText","hasVisibleContent","skeletonEnabled","trailSkeleton","createSkeletonPlaceholder","indicator","actions","wrapper","createBubbleWithLayout","reasoningExpansionState","appendRenderedValue","container","value","getReasoningPreviewText","message","maxLines","text","line","updateReasoningBubbleUI","messageId","bubble","expanded","header","content","preview","toggleIcon","chevronIcon","renderLucideIcon","createReasoningBubble","config","reasoning","createElement","reasoningDisplayConfig","expandable","isActive","previewText","headerContent","title","defaultSummary","reasoningConfig","startedAt","createElapsedSpan","span","computeReasoningElapsed","customSummary","status","describeReasonStatus","loadingAnimation","activeTemplate","completeTemplate","currentTemplate","skipCustomElement","renderFormattedTitle","template","animated","segments","parseFormattedTemplate","charIndex","seg","parent","w","s","appendCharSpans","animDuration","headerMeta","collapsedPreview","renderedPreview","body","toolExpansionState","appendRenderedValue","container","value","getToolPreviewText","message","maxLines","tool","chunkText","line","argsText","formatUnknownValue","applyToolCodeBlockColors","pre","toolCallConfig","getToolSummaryText","config","toolDisplayConfig","collapsedMode","previewText","defaultSummary","describeToolTitle","isActive","summary","resolveToolHeaderText","updateToolBubbleUI","messageId","bubble","expanded","header","content","preview","toggleIcon","iconColor","chevronIcon","renderLucideIcon","createToolBubble","createElement","expandable","headerContent","title","startedAt","createElapsedSpan","span","computeToolElapsed","customSummary","loadingAnimation","activeTemplate","completeTemplate","currentTemplate","skipCustomElement","renderFormattedTitle","template","animated","toolName","segments","parseFormattedTemplate","charIndex","seg","parent","w","s","text","appendCharSpans","animDuration","headerMeta","collapsedPreview","renderedPreview","argsBlock","argsLabel","argsPre","logsBlock","logsLabel","logsPre","resultBlock","resultLabel","resultPre","duration","approvalDetailsExpansionState","humanizeToolName","toolName","words","WEBMCP_TOOL_PREFIX","sentence","resolveApprovalConfig","config","isDetailsExpanded","messageId","detailsMode","applyDetailsToggleState","toggle","expanded","approvalConfig","label","chevronHolder","chevron","renderLucideIcon","updateApprovalDetailsUI","bubble","details","createApprovalBubble","message","config","approval","approvalConfig","isPending","bubble","createElement","header","iconContainer","iconName","iconColor","icon","renderLucideIcon","content","titleRow","title","badge","declaredTitle","WEBMCP_TOOL_PREFIX","getWebMcpToolDisplayTitle","summaryFromConfig","summaryFallsBackToDescription","summaryText","humanizeToolName","summary","reasonLine","reasonLabel","detailsMode","showDescriptionInDetails","hasDetails","expanded","isDetailsExpanded","toggle","toggleLabel","chevronHolder","applyDetailsToggleState","details","description","paramsPre","formatUnknownValue","buttonsContainer","approveBtn","approveIcon","approveLabel","denyBtn","denyIcon","denyLabel","defaultContainer","anchor","root","createPopover","options","content","placement","offset","matchAnchorWidth","zIndex","onOpen","onDismiss","container","open","detach","reposition","rect","top","left","close","doOpen","ownerWindow","ownerDocument","onReposition","onOutside","event","path","armId","isEditableEventTarget","el","createInstanceState","detachMessage","state","messageId","prevKey","popover","teardownMessage","idx","resolveApprovalConfig","config","isDetailsExpanded","approvalConfig","mode","approvalDetailsExpansionState","kbd","label","el","createElement","buildTitle","approval","title","declaredTitle","WEBMCP_TOOL_PREFIX","getWebMcpToolDisplayTitle","custom","toolDisplay","humanizeToolName","source","toolStrong","srcStrong","buildResolvedTrace","row","icon","renderLucideIcon","name","buildPending","message","approve","deny","enableAlways","card","detailsMode","hasDescription","hasParams","hasDetails","expanded","showDetailsLabel","hideDetailsLabel","head","logo","glyph","toggle","chevron","body","details","desc","pre","formatUnknownValue","reasonLine","actions","applyPrimaryColors","denyBtn","split","primary","caret","caretIcon","menu","once","createPopover","allow","e","target","action","willOpen","createBuiltInApprovalPlugin","hidden","onKeydown","isEditableEventTarget","id","createSuggestions","container","suggestionButtons","lastAgentShownKey","chips","session","textarea","messages","chipsConfig","opts","agentPushed","msg","fragment","streaming","getFontFamilyValue","family","chip","btn","createElement","shownKey","EventStreamBuffer","maxSize","store","event","events","toLoad","count","all","EventStreamStore","dbName","storeName","resolve","reject","request","db","err","event","events","store","toWrite","REQUEST_START_EVENTS","STEP_START_EVENTS","VISIBLE_DELTA_EVENTS","INTERMEDIATE_COMPLETE_EVENTS","TERMINAL_COMPLETE_EVENTS","ERROR_EVENTS","isRecord","value","toFiniteNumber","getRecord","key","nested","estimateTokensFromCharCount","charCount","calculateTokensPerSecond","outputTokens","durationMs","resolveEventType","eventType","payload","getTextDelta","isVisibleTextDelta","type","getOutputTokens","result","getRecord","candidates","candidate","toFiniteNumber","getExecutionTimeMs","defaultClock","ThroughputTracker","now","run","isRecord","ERROR_EVENTS","REQUEST_START_EVENTS","STEP_START_EVENTS","VISIBLE_DELTA_EVENTS","text","stats","estimateTokensFromCharCount","INTERMEDIATE_COMPLETE_EVENTS","exact","usingExact","TERMINAL_COMPLETE_EVENTS","terminalExact","source","streamedDurationMs","applyCustomClasses","el","classes","c","DEFAULT_BADGE_COLORS","DEFAULT_BADGE_COLOR","DEFAULT_DESCRIPTION_FIELDS","UPDATE_THROTTLE_MS","getBadgeColor","eventType","customColors","allColors","prefix","formatRelativeTimestamp","ms","firstEventMs","formatAbsoluteTimestamp","d","hh","mm","ss","mmm","extractDescription","payload","fields","obj","field","parts","current","part","copyToClipboard","text","resolve","textarea","formatEventForCopy","event","formattedPayload","formatThroughputValue","metric","formatThroughputMeta","renderInlinePayload","plugins","config","parsedPayload","payloadPlugin","p","customPayload","renderDefaultPayload","payloadContainer","createElement","payloadContent","renderEventRow","index","firstTimestamp","esConfig","expandedSet","onToggleExpand","isExpanded","wrapper","rowPlugin","customRow","buildDefaultRowContent","container","row","chevron","chevronIcon","renderLucideIcon","timestamp","tsFormat","seqNum","badgeColor","badge","descFields","desc","descEl","spacer","copyBtn","clipIcon","e","checkIcon","restoreIcon","createEventStreamView","options","buffer","getFullHistory","onClose","getThroughput","scrollToBottomConfig","scrollToBottomEnabled","scrollToBottomIconName","scrollToBottomLabel","viewPlugin","customView","buildDefaultView","customClasses","filteredEvents","selectedType","searchTerm","searchTimeout","lastKnownTypes","lastTypeCounts","lastFilteredCount","autoFollow","createFollowStateController","newEventsSincePause","lastRenderTime","pendingUpdate","pendingRafId","suppressScrollHandler","lastScrollTop","rowElements","lastRenderedFilter","lastRenderedSearch","dirtyExpandId","filterSelect","copyAllBtn","searchInput","searchClearBtn","throughputValueEl","throughputContainer","throughputTooltipEl","buildDefaultToolbar","toolbarOuter","headerBar","group","tooltip","showTooltip","gRect","pRect","hideTooltip","headerSpacer","allOption","copyAllIcon","copyAllLabel","searchBar","searchIcon","searchIconWrapper","clearSearchIcon","toolbar","toolbarPlugin","type","resetScrollState","updateNow","term","truncationBanner","updateThroughputSummary","value","meta","eventsListWrapper","eventsList","scrollIndicator","arrowIcon","indicatorText","noResultsMsg","updateFilterOptions","allEvents","typeCounts","types","typesChanged","t","i","countsChanged","totalChanged","a","b","currentValue","opt","getFilteredEvents","events","lower","hasActiveFilters","toggleExpand","eventId","savedScrollTop","wasAutoFollowing","isNearBottom","isElementNearBottom","evictedCount","newCount","bufferHasEvents","currentIds","id","filterChanged","isFirstRender","fragment","oldRow","idx","newRow","activeIds","evt","update","swapCopyAllIcon","iconName","restoreAfterMs","icon","label","original","restoreLabel","handleCopyAll","parsed","handleFilterChange","handleSearchInput","handleSearchClear","handleListScroll","currentScrollTop","action","nextLastScrollTop","resolveFollowStateFromScroll","handleWheel","resolveFollowStateFromWheel","handleScrollIndicatorClick","handleRowClick","target","handleKeyDown","destroy","createRovingTablist","container","options","orientation","prevKey","nextKey","tabs","restoreFocus","revealTab","tab","indexOfTarget","target","node","t","onKeydown","e","index","next","onFocusin","nextTabs","selectedIndex","i","selected","stop","createArtifactPane","config","options","layout","documentChrome","toolbarTitle","closeButtonLabel","panePadding","tabFadeSize","resolveFade","cfg","fadeStartEnabled","fadeEndEnabled","backdrop","createElement","dismissLocalUi","shell","copyMenuDropdown","toolbar","titleEl","closeBtn","createIconButton","viewMode","viewToggle","createToggleGroup","id","render","actionsRight","showCopyLabel","showCopyChevron","copyMenuItems","showCopyMenu","copyWrap","copyBtn","copyMenuChevronBtn","createLabelButton","chev","renderLucideIcon","refreshBtn","closeIconBtn","expandBtn","customActions","customActionList","getSelectedArtifactText","sel","records","r","selectedId","markdown","jsonPayload","defaultCopy","artifactCopyText","handler","resolvePortal","initDropdown","createDropdownMenu","item","actionId","e","centerTitle","defaultActions","list","currentRenderTabBar","customBarMount","tablist","createRovingTablist","index","updateTabFades","el","max","overflow","offset","tabFadeRaf","scheduleTabFades","run","content","mobileOpen","visible","dirty","lastScrolledTabId","customBarSig","preview","fileToggleMounted","resolveViewMode","rec","updateFileToggleVisibility","previewable","currentSelectedRecord","x","renderCustomActions","ctx","artifactRecordActionContext","buttons","action","buildArtifactActionButton","c","hideTabs","useCustom","sig","bar","tabEls","selectedIndex","tab","fileMeta","label","basenameOf","tooltip","activeTab","selFile","kind","fileTypeLabel","rawTitle","baseTitle","renderArtifactPreviewBody","applyLayoutVisibility","has","isMobile","flush","state","open","expanded","svg","actions","resolved","fn","next","artifactsSidebarEnabled","config","applyArtifactPaneBorderTheme","mount","config","artifactsSidebarEnabled","l","full","left","clearDocumentToolbarLayoutVars","ARTIFACT_APPEARANCE_MODES","resolveArtifactPaneAppearance","raw","isArtifactPaneAppearanceDetached","applyArtifactLayoutCssVars","gapDefault","paneBg","panePad","iconColor","toggleBg","toggleBorder","applyArtifactPaneAppearance","m","layout","mode","radius","shadow","chatShadow","shouldExpandLauncherForArtifacts","launcherEnabled","parseArtifactResizePx","input","fallback","m","parseArtifactResizeMaxPxOptional","clampArtifactPaneWidth","widthPx","minPx","maxPx","maxArtifactWidthFromSplit","splitWidthPx","gapPx","handleWidthPx","chatMinPx","raw","readFlexGapPx","splitRoot","win","first","m2","resolveArtifactPaneWidthPx","candidatePx","resizableMinWidth","resizableMaxWidth","cap","formDefinitions","enhanceWithForms","bubble","message","config","session","placeholders","placeholder","type","definition","heading","createElement","title","desc","form","field","group","label","inputType","control","actions","status","submit","event","formEndpoint","formData","payload","value","key","response","data","error","PluginRegistry","plugin","pluginId","a","b","instancePlugins","allPlugins","instanceIds","p","pluginRegistry","createEventBus","listeners","on","event","handler","off","payload","error","stripCodeFence","value","match","extractJsonObject","trimmed","start","depth","i","char","defaultJsonActionParser","text","withoutFence","jsonBody","parsed","action","payload","asString","defaultActionHandlers","context","selector","element","ensureArrayOfStrings","entry","createActionManager","options","processedIds","syncFromMetadata","persistProcessedIds","latestIds","prev","parseSource","acc","parser","eventPayload","handler","triggerResubmit","handlerResult","persist","error","safeJsonParse","value","error","sanitizeMessages","messages","message","sanitizeArtifacts","artifacts","artifact","createLocalStorageAdapter","key","getStorage","storage","state","payload","import_partial_json","isComponentDirective","obj","component","extractComponentDirective","parsed","rawJson","props","createComponentStreamParser","extractedDirective","processedLength","accumulatedContent","trimmed","parsePartialJson","directive","isComponentDirectiveType","renderComponentDirective","directive","options","config","message","onPropsUpdate","renderer","componentRegistry","context","newProps","error","createComponentMiddleware","parser","createComponentStreamParser","accumulatedContent","selectDirectiveSource","trimmed","hasComponentDirective","source","parsed","extractComponentDirectiveFromMessage","defaultCSATLabels","createCSATFeedback","options","onSubmit","onDismiss","title","subtitle","commentPlaceholder","submitText","skipText","showComment","ratingLabels","container","selectedRating","content","header","titleEl","subtitleEl","ratingContainer","ratingButtons","i","ratingButton","btn","index","isSelected","commentTextarea","commentContainer","actions","skipButton","submitButton","comment","error","createNPSFeedback","lowLabel","highLabel","labelsRow","lowLabelEl","highLabelEl","numbersRow","DEFAULT_CHAT_HISTORY_STORAGE_KEY","VOICE_STATE_RESTORE_WINDOW","ARTIFACT_SPLIT_DESKTOP_MIN","IMAGE_FILE_EXTENSION_BY_MIME_TYPE","getClipboardImageFiles","clipboardData","imageFiles","clipboardItems","item","file","extension","dataTransferHasFiles","dataTransfer","types","normalizePersistStateConfig","config","getPersistStorage","storageType","storage","testKey","ensureRecord","value","stripStreamingFromMessages","messages","message","buildPostprocessor","cfg","actionManager","onResubmitRequested","markdownProcessor","createMarkdownProcessorFromConfig","sanitize","resolveSanitizer","context","nextText","rawPayload","actionResult","parsersReady","getMarkdownParsersSync","html","out","source","stabilizeStreamingTables","escapeHtml","buildDropOverlay","dropCfg","overlay","createElement","iconName","iconSize","iconColor","iconStrokeWidth","iconSvg","renderLucideIcon","labelEl","createAgentExperience","mount","initialConfig","runtimeOptions","mergeWithDefaults","plugins","pluginRegistry","builtInApprovalPlugin","teardownBuiltInApprovals","createBuiltInApprovalPlugin","componentRegistry","eventBus","createEventBus","storageAdapter","createLocalStorageAdapter","persistentMetadata","pendingStoredState","shouldOpenAfterStateLoaded","applyStateLoadedHook","state","result","processedState","open","error","storedState","resolved","getSessionMetadata","updateSessionMetadata","updater","persistState","resolvedActionParsers","defaultJsonActionParser","resolvedActionHandlers","defaultActionHandlers","createActionManager","launcherEnabled","autoExpand","autoFocusInput","prevAutoExpand","prevLauncherEnabled","prevHeaderLayout","wasMobileFullscreen","isComposerBar","isComposerBarMountMode","isPanelToggleable","pendingResubmit","pendingResubmitTimeout","handleResubmitRequested","postprocess","showReasoning","showToolCalls","showEventStreamToggle","scrollToBottomFeature","scrollBehaviorFeature","eventStreamDbName","eventStreamStore","EventStreamStore","eventStreamMaxEvents","eventStreamBuffer","EventStreamBuffer","throughputTracker","ThroughputTracker","eventStreamView","eventStreamVisible","eventStreamRAF","eventStreamLastUpdate","err","messageActionCallbacks","session","feedback","statusConfig","_getStatusText","status","statusCopy","applyStatusToElement","el","text","statusCfg","link","view","createWidgetView","wrapper","panel","pillRoot","panelElements","container","body","messagesWrapper","suggestions","textarea","sendButton","sendButtonWrapper","composerForm","statusText","introTitle","introSubtitle","closeButton","iconHolder","headerTitle","headerSubtitle","header","footer","_actionsRow","leftActions","rightActions","setSendButtonMode","micButton","micButtonWrapper","attachmentButton","attachmentButtonWrapper","attachmentInput","attachmentPreviewsContainer","SCROLL_TO_BOTTOM_EDGE_OFFSET","getScrollToBottomLabel","getScrollToBottomIconName","isScrollToBottomEnabled","getScrollMode","isFollowEffective","followFallbackActive","getAnchorTopOffset","getScrollRestorePosition","isPauseOnInteractionEnabled","isActivityWhilePinnedEnabled","isAnnounceEnabled","scrollToBottomButton","scrollToBottomIcon","scrollToBottomLabel","scrollToBottomCount","anchorSpacer","liveRegion","announceTimer","pendingAnnouncement","announce","updateScrollToBottomButtonOffset","footerHeight","renderScrollToBottomButton","hasLabel","icon","attachmentManager","composerVoiceBridge","headerPlugin","p","customHeader","headerElements","buildHeader","attachHeaderToContainer","setOpenState","existingHeader","toggleEventStreamOn","createEventStreamView","toggleEventStreamOff","eventStreamToggleBtn","HEADER_THEME_CSS","activeClasses","c","rafLoop","now","syncScrollToBottomButton","esClassNames","toggleBtnClasses","activityIcon","clearChatWrapper","closeWrapper","insertBefore","ensureComposerAttachmentSurface","rootFooter","att","previews","form","fileIn","ALL_SUPPORTED_MIME_TYPES","composerPlugin","composerCfg","customComposer","buildComposer","hasAttachments","maybeExpandComposerBar","contentParts","createTextPart","modelId","bindComposerRefsFromFooter","pick","selectors","selector","found","ta","sb","mic","st","sug","attBtn","ar","contentMaxWidth","AttachmentManager","e","target","slots","getDefaultSlotContent","slot","insertSlotContent","element","titleSection","introCard","slotName","renderer","slotElement","handleBubbleExpansion","event","headerButton","bubble","messageId","bubbleType","reasoningExpansionState","updateReasoningBubbleUI","toolExpansionState","updateToolBubbleUI","defaultExpanded","expanded","approvalDetailsExpansionState","updateApprovalDetailsUI","messageCache","root","selection","raw","normalized","normalizeCopiedSelectionText","messageVoteState","readAloudActiveId","readAloudActiveState","READ_ALOUD_ICONS","applyReadAloudButton","btn","label","svg","refreshReadAloudButtons","id","actionBtn","actionsContainer","action","m","textToCopy","checkIcon","originalIcon","wasActive","outlineIcon","oppositeAction","oppositeBtn","filledIcon","approvalButton","approvalBubble","decision","approvalMessage","buttonsContainer","artifactPaneApi","artifactPanelResizeObs","syncPanelChrome","appliedSplitMode","weldedOuterRadius","lastArtifactsState","artifactsPaneUserHidden","artifactPaneExpanded","artifactPaneExpandedPinned","artifactsPaneUserOpened","artifactPaneCanShow","a","resolveArtifactDisplayMode","artifactPaneVisible","sessionRef","resolveCardArtifactContent","fromEl","artifactId","artifact","markdown","title","artifactType","msgId","msg","parsed","dlBtn","filename","mime","content","downloadInfoFor","blob","url","actionId","inlineEl","cardEl","ctx","copyBtn","artifactCopyText","copyIcon","expandBtn","syncArtifactPane","card","askUserOverlay","submitAskUserAnswer","sheet","meta","trimmed","toolCallId","isFreeText","removeAskUserQuestionSheet","sourceMessage","persistGroupedProgress","buildStructuredAnswers","getCurrentIndex","stringifyStructured","answers","q","v","maybeAutoAdvance","idx","count","getQuestionCount","navigateToPage","trigger","multiSelect","grouped","isGroupedSheet","stored","readAnswersFromSheet","set","setCurrentAnswer","pressed","submitBtn","getSelectedLabels","labels","row","pending","direction","nextIdx","structured","summary","isFinal","freeInput","input","handleAskUserDigitKey","n","target_pill","artifactSplitRoot","artifactResizeHandle","artifactResizeUnbind","artifactResizeDocEnd","reconcileArtifactResize","artifactExpandedApplied","artifactStashedWidth","artifactStashedMaxWidth","stopArtifactResizePointer","positionExtensionArtifactResizeHandle","ext","ownerWin","mobile","chat","hitW","gapPx","readFlexGapPx","left","applyLauncherArtifactPanelWidth","isMobileFullscreenActive","win","mobileFullscreen","mobileBreakpoint","isDockedMountMode","splitChromeMode","artifactsSidebarEnabled","isArtifactPaneAppearanceDetached","applyArtifactLayoutCssVars","applyArtifactPaneAppearance","threshold","w","paneVisible","expandToggleEnabled","paneEl","modeNow","chatColumn","splitRoot","createArtifactPane","next","handle","doc","onPointerDown","startX","startW","layout","onMove","ev","splitW","weldedChrome","handleW","clamped","resolveArtifactPaneWidthPx","onUp","has","resolveDockConfig","ownerWindow","shouldExpandLauncherForArtifacts","base","DEFAULT_FLOATING_LAUNCHER_WIDTH","applyFullHeightStyles","cb","isExpanded","expandedSize","panelPartial","activeTheme","getActiveTheme","resolveCb","fallback","resolveTokenValue","defaultBorder","defaultShadow","defaultRadius","dockedMode","sidebarMode","fullHeight","isInlineEmbed","isDetached","resolvePanelChrome","isMobileViewport","shouldGoFullscreen","dockedHostFullscreen","position","isLeftSidebar","overlayZIndex","DEFAULT_OVERLAY_Z_INDEX","cardBorder","cardShadow","cardRadius","detachedCard","defaultPanelBorder","defaultPanelShadow","defaultPanelBorderRadius","panelBorder","panelShadow","panelBorderRadius","splitMode","detachedSplitActive","weldedSplitActive","chatCardShadow","chatFlush","outerPanelShadow","chatContainerBorder","chatContainerRadius","panelRadiusExplicit","appliedPanelRadius","prevBodyScrollTop","restoreBodyScrollTop","maxScrollTop","width","dw","wl","positionMap","cls","sidebarWidth","maxHeightStyles","paddingStyles","zIndexStyles","applyThemeVariables","destroyCallbacks","teardownHostStacking","releaseScrollLock","cleanupThemeObserver","setupThemeObserver","createThemeObserver","streamAnimationConfig","plugin","resolveStreamAnimationPlugin","ensurePluginActive","detachAllPlugins","suggestionsManager","createSuggestions","closeHandler","renderSuggestions","current","agentChips","latestAgentSuggestions","isStreaming","createMessageCache","lastAskBubbleFingerprint","lastComponentDirectiveFingerprint","lastApprovalBubbleFingerprint","configVersion","markdownReadyAtInit","autoFollow","createFollowStateController","lastScrollTop","scrollRAF","isAutoScrolling","hasPendingAutoScroll","newMessagesSincePause","anchorState","anchorRAF","scrollSendSeeded","suppressScrollSend","lastSentUserMessageId","currentTurnAnchored","lastHandledAssistantId","USER_SCROLL_THRESHOLD","BOTTOM_THRESHOLD","AUTO_SCROLL_SNAP_THRESHOLD","messageState","voiceState","voiceAutoResumeMode","emitVoiceState","persistVoiceMetadata","prev","maybeRestoreVoiceFromMetadata","rawVoiceState","timestamp","applyRuntypeMicRecordingStyles","startVoiceRecognition","getMessagesForPersistence","storageMutationTail","reportStorageMutationError","runStorageMutation","mutation","errorLabel","run","prior","tail","messagesOverride","payload","smoothScrollRAF","getScrollableContainer","cancelSmoothScroll","cancelAutoScroll","isStreamingOutOfView","isAwayFromLatest","updateScrollToBottomCountBadge","streamingBelow","resetNewMessagesCount","isElementNearBottom","show","getScrollBottomOffset","pauseAutoScroll","resumeAutoScroll","scheduleAutoScroll","force","smoothScrollToBottom","animateScrollTo","resolveTarget","duration","shouldContinue","start","distance","startTime","easeOutCubic","t","animate","currentTime","currentTarget","elapsed","progress","eased","currentScroll","jumpToBottomInstant","offsetTopWithinBody","top","node","restoreScrollPosition","lastUser","escapedId","setAnchorSpacerHeight","height","resetAnchorState","scheduleAnchorToUserMessage","anchorOffsetTop","previousSpacerHeight","contentHeight","targetScrollTop","spacerHeight","computeAnchorScrollState","handleContentResize","currentContentHeight","computeShrunkSpacerHeight","handleUserMessageSent","mode","handleAssistantTurnStarted","trackMessages","nextState","previous","key","renderMessagesWithPluginsImpl","transform","tempContainer","inlineLoadingRenderer","loadingPlugin","appendRenderedValue","containerEl","activeMessageIds","liveAskToolIds","hasAskPlugin","askPluginHydrate","componentDirectiveHydrate","componentStreamingEnabled","hasApprovalPlugin","approvalPluginHydrate","askWithPlugin","isAskUserQuestionMessage","approvalWithPlugin","hasDirectiveBubble","hasComponentDirective","askMeta","fingerprint","computeMessageFingerprint","cachedWrapper","getCachedWrapper","ensureAskUserQuestionSheet","matchingPlugin","messageLayoutConfig","isSuggestRepliesMessage","askPlugin","lastFp","needsRebuild","pluginBubble","complete","parseAskUserQuestionPayload","liveMessage","answer","live","previouslyMounted","stub","approvalPlugin","liveBubble","approvalMessageId","resolveDecision","options","createApprovalBubble","createReasoningBubble","createToolBubble","b","createStandardBubble","enhanceWithForms","directive","extractComponentDirectiveFromMessage","wrapChrome","componentBubble","renderComponentDirective","fresh","hasLiveInlineArtifactBlock","componentWrapper","textDiv","stack","setCachedWrapper","toolGroups","currentGroup","group","groupIndex","wrappers","groupMessage","child","groupWrapper","groupContainer","defaultSummary","renderedSummary","toolCall","summaryOnly","wrapperIndex","pruneCache","hasStreamingAssistantMessage","lastMessage","hasRecentAssistantResponse","loadingIndicatorContext","createTypingIndicator","typingIndicator","typingBubble","showBubble","typingWrapper","idleIndicatorContext","idlePlugin","idleIndicator","idleBubble","idleWrapper","morphMessages","renderMessagesWithPlugins","composerBarOutsideClickListener","attachComposerBarOutsideClickDismiss","listener","path","detachComposerBarOutsideClickDismiss","composerBarEscapeListener","attachComposerBarEscapeDismiss","detachComposerBarEscapeDismiss","composerHovered","peekActivatedPlugins","resolvePeekStreamAnimationFeature","peekFeature","syncComposerBarPeek","peekBanner","peekTextNode","lastAssistant","i","streaming","feature","streamAnimation","resolveStreamAnimation","pluginStillAnimating","animationActive","desiredContainerClass","currentContainerClass","buffered","applyStreamBuffer","skeleton","createSkeletonPlaceholder","sliceStart","slice","escaped","preview","wrapStreamAnimation","caret","createStreamCaret","spans","lastSpan","shouldShow","onPeekPointerDown","onPanelPointerEnter","onPanelPointerLeave","applyComposerBarGeometry","isOpen","bottomOffset","collapsedMaxWidth","expandedMaxWidth","expandedTopOffset","modalMaxWidth","modalMaxHeight","viewportClamp","pillAreaClearance","s","ps","updateOpenState","dockReveal","launcherButtonInstance","customLauncherElement","nextOpen","prevOpen","isViewportCovering","sm","ow","mf","mb","isMobile","dockedMF","composerBarFS","hostEl","syncOverlayHostStacking","acquireScrollLock","recalcPanelHeight","stateEvent","setComposerDisabled","disabled","maybeFocusInput","updateCopy","showCard","storedId","sessionId","toolElapsedTimerId","ensureToolElapsedTimer","span","startedAt","formatElapsedMs","isDeepEqual","right","index","leftRecord","rightRecord","leftKeys","rightKeys","withoutStreamingText","_content","_rawContent","_llmContent","rest","isPlainStreamingAssistant","isPureStreamingTextProgression","candidate","before","after","lastAppliedMessages","activeStreamingTextCandidate","pendingStreamingTextMessages","streamingTextRAF","applyMessagesChanged","lastUserMessage","lastAssistantMessage","updateInlineArtifactBlocks","prevLastUserMessageId","flushPendingStreamingText","discardPendingStreamingText","handleMessagesChanged","comparison","AgentWidgetSession","currentStatusConfig","removeRuntypeMicStateStyles","applyRuntypeMicProcessingStyles","applyRuntypeMicSpeakingStyles","executionId","lastEventId","lastReadAloudId","activeId","type","maybeBootResume","handleSubmit","resetHistoryNavigation","historyNavigationEnabled","composerHistoryState","INITIAL_HISTORY_STATE","suppressHistoryReset","getUserMessageHistory","applyHistoryValue","end","handleComposerInput","handleComposerKeydown","atStart","navigateComposerHistory","handleEscStop","handleInputPaste","clipboardImageFiles","speechRecognition","isRecording","pauseTimer","originalMicStyles","getSpeechRecognitionClass","SpeechRecognitionClass","pauseDuration","initialText","fullTranscript","interimTranscript","transcript","newValue","finalValue","stopVoiceRecognition","voiceConfig","recordingBackgroundColor","recordingIconColor","recordingBorderColor","createMicButton","sendButtonConfig","hasSpeechRecognition","hasRuntypeProvider","hasCustomProvider","micIconName","buttonSize","micIconSize","micIconSizeNum","backgroundColor","micIconSvg","tooltipText","tooltip","storeOriginalMicStyles","swapMicIcon","color","existingSvg","size","newSvg","removeAllVoiceStateClasses","interruptionMode","bgColor","borderColor","resolvedColor","defaultSpeakingIcon","ariaLabel","handleMicButtonClick","voiceStatus","autoResumeUnsub","resubmitUnsub","toggleOpen","instance","resolveLauncher","chromeResynced","viewportHeight","verticalMargin","heightOffset","available","finalHeight","isVC","footerResizeObserver","lastBottomOffset","getTranscriptSelection","hasActiveTranscriptSelection","hasSelectionWithin","handleScroll","scrollTop","currentBottomOffset","bottomOffsetShrank","nextLastScrollTop","resolveFollowStateFromScroll","contentResizeObserver","handleSelectionChange","selectionDocument","NAV_KEYS","handleTranscriptKeydown","handleTranscriptFocusIn","handleWheel","resolveFollowStateFromWheel","refreshCloseButton","clearChatButton","clearEvent","escStopDoc","ATTACHMENT_DROP_ACTIVE_CLASS","attachmentFileDragDepth","clearAttachmentDropVisual","attachmentDropHandlingActive","handleAttachmentDragEnterCapture","handleAttachmentDragLeaveCapture","handleAttachmentDragOverCapture","handleAttachmentDropCapture","files","attachmentDropCapture","ownerDoc","handleDocDragOver","handleDocDrop","controller","nextConfig","previousToolCallConfig","previousMessageActions","previousLayoutMessages","previousColorScheme","previousLoadingIndicator","previousIterationDisplay","previousShowReasoning","previousShowToolCalls","previousToolCallDisplay","previousReasoningDisplay","previousStreamAnimationType","newPlugins","prevScrollMode","prevShowEventStreamToggle","dynEsClassNames","dynToggleBtnClasses","headerLayoutConfig","newHeaderElements","buildHeaderWithLayout","showClearChat","closeButtonWrapper","showHeader","showFooter","toolCallConfigChanged","messageActionsChanged","layoutMessagesChanged","loadingIndicatorChanged","iterationDisplayChanged","featuresChanged","nextStreamAnimationType","streamAnimationPlugin","launcher","headerIconHidden","layoutShowIcon","shouldHideIcon","headerIconName","headerIconSize","headerEl","headerCopy","img","newImg","existingImg","layoutShowTitle","layoutShowSubtitle","closeButtonSize","closeButtonPlacement","isTopRight","currentlyTopRight","clearChatPlacement","clearChatEnabled","borderWidth","closeButtonIconName","closeButtonIconText","closeButtonTooltipText","closeButtonShowTooltip","portaledTooltip","showTooltip","tooltipDocument","tooltipContainer","createElementInDocument","arrow","buttonRect","PORTALED_OVERLAY_Z_INDEX","hideTooltip","clearChatButtonWrapper","clearChatConfig","layoutShowClearChat","shouldShowClearChat","closeButtonWrapperEl","clearChatSize","clearChatIconName","clearChatIconColor","clearChatIconSize","clearChatTooltipText","clearChatShowTooltip","nextParsers","nextHandlers","voiceRecognitionEnabled","micButtonResult","newTooltip","attachmentsConfig","attachIconName","attachIconSize","buttonSizeNum","attachIconSizeNum","attachIconSvg","attachTooltipText","useIcon","iconText","textColor","updatedContentMaxWidth","statusIndicatorConfig","isVisible","currentStatus","alignClass","valueToSubmit","optionsList","stream","manual","approvalId","handler","existingFeedback","feedbackEl","createCSATFeedback","rating","comment","createNPSFeedback","previousDebug","debugApi","active","instanceId","handleFocusInput","detail","handleShowEvent","handleHideEvent","handleShowArtifacts","handleHideArtifacts","handleUpsertArtifact","handleSelectArtifact","handleClearArtifacts","persistConfig","openKey","voiceKey","voiceModeKey","wasOpen","wasVoiceActive","wasInVoiceMode","clearPersistState","handleClearChat","unsubscribeParsersReady","onMarkdownParsersReady","parseDockWidthToPx","width","shellClientWidth","w","px","pct","applyDockSlotMaxHeight","dockSlot","maxHeight","applyInFlowDockSlotPosition","warnIfDockHeightChainUnresolved","shell","dock","parent","probe","measurable","resolved","setDirectHostStyles","host","config","launcherEnabled","clearOverlayDockSlotStyles","clearMobileFullscreenDockSlotStyles","clearResizeDockSlotTransition","clearPushTrackStyles","pushTrack","resetContentSlotFlexSizing","contentSlot","clearEmergeDockStyles","migrateDockChildren","usePush","orderDockChildren","side","resolveDetachedSlotStyles","theme","getActiveTheme","panel","resolve","raw","fallback","resolveTokenValue","DEFAULT_PANEL_INSET","DEFAULT_PANEL_CANVAS_BACKGROUND","applyDetachedDockSlotChrome","detached","inset","canvasBackground","applyDockStyles","expanded","detachedSlotStyles","resolveDockConfig","detachedInset","detachedCanvas","ownerWindow","mobileFullscreenEnabled","mobileBreakpoint","isMobileViewport","DEFAULT_OVERLAY_Z_INDEX","dockTransition","translateClosed","translate","panelPx","contentPx","marginOffsetPx","isEmerge","collapsedClosed","emergeHostWidth","createDirectLayout","target","nextConfig","createDockedLayout","ownerDocument","originalParent","tagName","originalNextSibling","resizeObserver","disconnectResizeObserver","refreshDetachedSlotStyles","reapplyDockStyles","cleanupSchemeObserver","syncSchemeObserver","createThemeObserver","syncPushResizeObserver","heightChainChecked","layout","onViewportResize","state","nextExpanded","createWidgetHostLayout","isDockedMountMode","ensureTarget","target","element","mountStyles","root","ownerDocument","globalLink","clonedLink","initAgentWidget","options","useShadow","config","hostLayout","createWidgetHostLayout","controller","stateUnsubs","createMount","host","nextConfig","shouldFillHost","isDockedMountMode","mount","shadowRoot","syncHostState","bindHostState","unsubscribe","mountController","createAgentExperience","destroyCurrentController","rebuildLayout","handleBase","mergedConfig","previousDocked","nextDocked","previousComposerBar","isComposerBarMountMode","nextComposerBar","handle","targetObject","prop","receiver","value","SKIP_TAGS","INTERACTIVE_TAGS","INTERACTIVE_ROLES","CARD_HINT_RE","CURRENCY_RE","BASE_SCORE_INTERACTIVE","BASE_SCORE_STATIC","hasCardHint","el","cls","i","a","subtreeHasCurrency","subtreeHasNonTrivialLink","anchors","href","subtreeHasButtonLike","extractFirstPrice","text","m","extractTitleAndHref","link","heading","extractCtaLabels","labels","push","s","t","b","inp","COMMERCE_CARD_RULE_ID","RESULT_CARD_RULE_ID","commerceCardScore","resultCardScore","defaultParseRules","owner","descendant","enriched","title","price","ctas","warnSimpleWithRules","resolveDomContextConfig","options","nested","maxElements","excludeSelector","maxTextLength","visibleOnly","root","mode","maxCandidates","rules","cssEscape","str","DATA_ATTR_PRIORITY","classifyInteractivity","tag","role","isElementVisible","style","collectAttributes","attrs","id","ariaLabel","type","value","name","attr","generateStableSelector","sel","val","classes","c","classSel","parent","index","nthSel","baseInteractivityScore","interactivity","buildEnriched","scoreCandidate","ctx","score","formattingRule","rule","bonus","shouldSuppress","kept","cand","k","collectStructured","cfg","rootEl","seenSelectors","raw","domIndex","walker","node","hasText","hasMeaningfulAttrs","sa","sb","formattedSummary","line","out","collectSimple","elements","interactive","staticEls","collectEnrichedPageContext","TEXT_PREVIEW_LEN","formatEnrichedContext","sections","summaries","groups","lines","accessibilityPlugin","theme","animationsPlugin","brandPlugin","brandConfig","newPalette","adjustColor","reducedMotionPlugin","resolved","highContrastPlugin","hex","factor","r","g","b","nr","ng","nb","createPlugin","config","SHOP_THEME","PANEL_EDGELESS_THEME","PRESET_SHOP","DEFAULT_FLOATING_LAUNCHER_WIDTH","PRESET_MINIMAL","PRESET_FULLSCREEN","PRESETS","getPreset","id","index_core_default","initAgentWidget","serializeHook","hook","fn","normalizeHooks","hooks","TEMPLATE_MARKDOWN_JSON_PARSER_TS","TEMPLATE_MARKDOWN_JSON_PARSER_ES5","TEMPLATE_NAV_THEN_CLICK_HANDLER_TS","TEMPLATE_NAV_THEN_CLICK_HANDLER_ES5","TEMPLATE_STREAM_PARSER_CALLBACK_TS","TEMPLATE_STREAM_PARSER_CALLBACK_ES5","detectParserTypeFromStreamParser","streamParser","fnString","getParserTypeFromConfig","config","generateToolCallConfig","indent","lines","key","value","generateMessageActionsConfig","hasSerializableProps","hasHookCallbacks","generateMarkdownConfig","hasOptions","hasDisableDefaultStyles","generateLayoutConfig","hasHeader","hasMessages","avatarKey","avatarValue","k","tsKey","tsValue","generateHooksConfig","appendSerializableObjectEntries","entryValue","appendSerializableObjectBlock","emitTargetSelector","options","generateCodeSnippet","format","cleanConfig","normalizedOptions","generateESMCode","generateScriptInstallerCode","generateScriptAdvancedCode","generateReactComponentCode","generateReactAdvancedCode","generateScriptManualCode","parserType","shouldEmitParserType","chip","buildSerializableConfig","serializableConfig","toolCallConfig","messageActionsConfig","markdownConfig","layoutConfig","headerConfig","messagesConfig","tsConfig","payload","configJson","VERSION","line","DEVICE_DIMENSIONS","ZOOM_STEP","ZOOM_MIN","ZOOM_MAX","STAGE_PADDING","SHADOW_MARGIN","CAROUSEL_CSS","injectStyles","style","computeFitScale","stage","dims","availW","availH","applyScale","wrapper","iframe","scale","device","createDemoCarousel","container","options","items","initialIndex","initialDevice","initialColorScheme","showZoomControls","showDeviceToggle","showColorSchemeToggle","onChange","currentIndex","currentDevice","currentScheme","zoomOverride","lastAutoScale","destroyed","root","createElement","toolbar","toolbarLead","toolbarTrail","prevBtn","createIconButton","navigate","titleWrap","titleBtn","titleText","titleChevron","chevronSvg","renderLucideIcon","dropdown","dropdownOpen","buildDropdownItems","i","item","btn","titleSpan","desc","closeDropdown","goTo","toggleDropdown","e","onDocClick","nextBtn","counterEl","deviceToggle","createToggleGroup","id","rescale","zoomLevelEl","zoomWrap","zoomOut","zoomIn","zoomFit","sep","schemeToggle","applySchemeToIframe","sep2","openBtn","body","updateDisplay","delta","next","index","resizeObserver","destroy","scheme","zoom"]}