@heroui/agent 0.2.0-beta.2 → 0.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,14 +33,13 @@ import {
33
33
  shouldRestoreActiveAgentPermissionMode,
34
34
  storeAgentPermissionMode,
35
35
  takeAgentShellHandoff
36
- } from "./chunk-EXFDD3K3.js";
36
+ } from "./chunk-XBYPU7PM.js";
37
37
  import {
38
38
  ActionButton,
39
39
  CodeBlock,
40
- ComponentSkeleton,
41
40
  ScrollShadow,
42
41
  renderInlineMarkdown
43
- } from "./chunk-CU6MPKAJ.js";
42
+ } from "./chunk-6O3SFXJK.js";
44
43
  import {
45
44
  CardVariantProvider,
46
45
  resolveOverlayPortalContainer
@@ -1722,12 +1721,12 @@ import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
1722
1721
  import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
1723
1722
  import {
1724
1723
  useCallback as useCallback11,
1725
- useEffect as useEffect12,
1724
+ useEffect as useEffect14,
1726
1725
  useEffectEvent,
1727
1726
  useLayoutEffect as useLayoutEffect4,
1728
1727
  useMemo as useMemo4,
1729
- useRef as useRef14,
1730
- useState as useState13
1728
+ useRef as useRef15,
1729
+ useState as useState14
1731
1730
  } from "react";
1732
1731
 
1733
1732
  // src/contracts/identity.ts
@@ -1778,7 +1777,7 @@ var agentModelIdSchema = z2.preprocess(
1778
1777
 
1779
1778
  // src/contracts/version.ts
1780
1779
  var HEROUI_AGENT_PROTOCOL_VERSION = 5;
1781
- var HEROUI_AGENT_SDK_VERSION = "0.2.0-beta.2";
1780
+ var HEROUI_AGENT_SDK_VERSION = "0.2.0-beta.4";
1782
1781
  var HEROUI_AGENT_TASK_ID = "heroui-agents-runtime";
1783
1782
 
1784
1783
  // src/contracts/identity.ts
@@ -1910,12 +1909,6 @@ var agentProjectConfigSchema = z3.object({
1910
1909
  */
1911
1910
  presence: z3.object({ appId: z3.uuid() }).optional(),
1912
1911
  protocolVersion: z3.literal(HEROUI_AGENT_PROTOCOL_VERSION),
1913
- /**
1914
- * Internal streaming infrastructure endpoint, resolved server-side so
1915
- * customers never configure it. Optional for compatibility with older API
1916
- * deployments; the SDK falls back to Trigger.dev's public endpoint.
1917
- */
1918
- realtime: z3.object({ url: z3.url() }).optional(),
1919
1912
  sdkVersion: z3.string().default(HEROUI_AGENT_SDK_VERSION),
1920
1913
  suggestedPrompts: z3.array(z3.string().trim().min(1).max(160)).max(5),
1921
1914
  surfaceVariant: agentSurfaceVariantSchema.default("plain"),
@@ -1990,7 +1983,7 @@ function collectActivity(message, clientTools, options = {}) {
1990
1983
  if (renderTool && toolPart.state === "output-error" && (options.isStreaming || laterRenderSucceeded)) {
1991
1984
  continue;
1992
1985
  }
1993
- const baseLabel = renderTool ? "Building your data view" : toolName === "executeSandbox" ? "Analyzing your data" : clientTools.get(toolName)?.displayName ?? humanizeToolName(toolName);
1986
+ const baseLabel = renderTool ? "Building your data view" : toolName === "executeSandbox" ? "Analyzing your data" : toolName === "getComponentSchema" ? "Preparing your data view" : clientTools.get(toolName)?.displayName ?? humanizeToolName(toolName);
1994
1987
  const rejected = Boolean(
1995
1988
  toolPart.output && typeof toolPart.output === "object" && "rejected" in toolPart.output && toolPart.output.rejected
1996
1989
  );
@@ -2000,7 +1993,7 @@ function collectActivity(message, clientTools, options = {}) {
2000
1993
  items.push({
2001
1994
  active: toolPart.state === "input-available" || toolPart.state === "input-streaming",
2002
1995
  id: toolPart.toolCallId || `${message.id}-tool-${index}`,
2003
- kind: renderTool ? "chart" : toolName === "executeSandbox" ? "code" : inferActivityKind(label),
1996
+ kind: renderTool ? "chart" : toolName === "executeSandbox" ? "code" : toolName === "getComponentSchema" ? "thinking" : inferActivityKind(label),
2004
1997
  label
2005
1998
  });
2006
1999
  }
@@ -2018,7 +2011,10 @@ import {
2018
2011
  Wrench
2019
2012
  } from "@gravity-ui/icons";
2020
2013
  import { Disclosure as Disclosure2 } from "@heroui/react";
2021
- import { useEffect as useEffect6, useRef as useRef5, useState as useState4 } from "react";
2014
+ import { useEffect as useEffect7, useRef as useRef6, useState as useState4 } from "react";
2015
+
2016
+ // src/embed/animated-status-text.tsx
2017
+ import { useEffect as useEffect5, useRef as useRef5 } from "react";
2022
2018
 
2023
2019
  // src/embed/shimmer-text.tsx
2024
2020
  import { jsx as jsx6 } from "react/jsx-runtime";
@@ -2028,7 +2024,17 @@ function ShimmerText({ children, className, ...props }) {
2028
2024
 
2029
2025
  // src/embed/animated-status-text.tsx
2030
2026
  import { jsx as jsx7 } from "react/jsx-runtime";
2031
- function AnimatedStatusText({ children, className, ...props }) {
2027
+ function AnimatedStatusText({
2028
+ animateOnMount = true,
2029
+ children,
2030
+ className,
2031
+ ...props
2032
+ }) {
2033
+ const previousValueRef = useRef5(children);
2034
+ const shouldAnimate = animateOnMount || previousValueRef.current !== children;
2035
+ useEffect5(() => {
2036
+ previousValueRef.current = children;
2037
+ }, [children]);
2032
2038
  return /* @__PURE__ */ jsx7(
2033
2039
  "span",
2034
2040
  {
@@ -2040,7 +2046,7 @@ function AnimatedStatusText({ children, className, ...props }) {
2040
2046
  "span",
2041
2047
  {
2042
2048
  "aria-hidden": "true",
2043
- className: "ha-status-text__value",
2049
+ className: shouldAnimate ? "ha-status-text__value ha-status-text__value--animated" : "ha-status-text__value",
2044
2050
  "data-slot": "animated-text-value",
2045
2051
  children: /* @__PURE__ */ jsx7(ShimmerText, { children })
2046
2052
  },
@@ -2051,7 +2057,7 @@ function AnimatedStatusText({ children, className, ...props }) {
2051
2057
  }
2052
2058
 
2053
2059
  // src/embed/use-scroll-shadow.ts
2054
- import { useEffect as useEffect5 } from "react";
2060
+ import { useEffect as useEffect6 } from "react";
2055
2061
  function getScrollShadowState(element, offset = 1) {
2056
2062
  const hasScrollBefore = element.scrollTop > offset;
2057
2063
  const hasScrollAfter = element.scrollTop + element.clientHeight + offset < element.scrollHeight;
@@ -2073,7 +2079,7 @@ function applyScrollShadowState(element, state) {
2073
2079
  }
2074
2080
  }
2075
2081
  function useScrollShadow(ref, offset = 1) {
2076
- useEffect5(() => {
2082
+ useEffect6(() => {
2077
2083
  const element = ref.current;
2078
2084
  if (!element) return;
2079
2085
  let animationFrame = null;
@@ -2108,8 +2114,8 @@ function useScrollShadow(ref, offset = 1) {
2108
2114
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
2109
2115
  var FOLLOW_THRESHOLD_PX = 24;
2110
2116
  function useFollowNewestStep(ref, stepCount, isStreaming, isExpanded) {
2111
- const isFollowingRef = useRef5(true);
2112
- useEffect6(() => {
2117
+ const isFollowingRef = useRef6(true);
2118
+ useEffect7(() => {
2113
2119
  const element = ref.current;
2114
2120
  if (!element) return;
2115
2121
  const handleScroll = () => {
@@ -2119,7 +2125,7 @@ function useFollowNewestStep(ref, stepCount, isStreaming, isExpanded) {
2119
2125
  element.addEventListener("scroll", handleScroll, { passive: true });
2120
2126
  return () => element.removeEventListener("scroll", handleScroll);
2121
2127
  }, [isExpanded, ref]);
2122
- useEffect6(() => {
2128
+ useEffect7(() => {
2123
2129
  const element = ref.current;
2124
2130
  if (!element || !isExpanded || !isStreaming || !isFollowingRef.current) return;
2125
2131
  element.scrollTop = element.scrollHeight;
@@ -2142,15 +2148,8 @@ function formatActivityDuration(durationMs) {
2142
2148
  if (minutes > 0) return `${minutes}m ${seconds}s`;
2143
2149
  return `${seconds}s`;
2144
2150
  }
2145
- function ActivityStatusIcon({ kind }) {
2146
- const Icon = kind === "complete" ? CircleCheck : activityIcons[kind];
2147
- return /* @__PURE__ */ jsx8("span", { className: "ha-activity-status-icon", children: /* @__PURE__ */ jsx8(Icon, { "aria-hidden": "true" }) }, kind);
2148
- }
2149
- function ActivityStatus({ label }) {
2150
- return /* @__PURE__ */ jsxs5("div", { className: "ha-progress ha-activity-status", children: [
2151
- /* @__PURE__ */ jsx8(ActivityStatusIcon, { kind: inferActivityKind(label) }),
2152
- /* @__PURE__ */ jsx8(AnimatedStatusText, { className: "ha-progress-shimmer", role: "status", children: label })
2153
- ] });
2151
+ function ActivityStatus({ ariaLabel, label }) {
2152
+ return /* @__PURE__ */ jsx8("div", { className: "ha-progress ha-activity-status", children: /* @__PURE__ */ jsx8(AnimatedStatusText, { "aria-label": ariaLabel, className: "ha-progress-shimmer", role: "status", children: label }) });
2154
2153
  }
2155
2154
  function ActivityTrail({
2156
2155
  completedAt,
@@ -2161,26 +2160,25 @@ function ActivityTrail({
2161
2160
  startedAt
2162
2161
  }) {
2163
2162
  const [isExpanded, setIsExpanded] = useState4(defaultOpen);
2164
- const panelRef = useRef5(null);
2163
+ const panelRef = useRef6(null);
2165
2164
  const activeItem = items.findLast((item) => item.active);
2166
2165
  const latestItem = items.at(-1);
2167
2166
  const activeLabel = activeItem?.label ?? latestItem?.label ?? progress ?? "Understanding your request\u2026";
2168
2167
  const elapsedMs = startedAt !== void 0 && !isStreaming ? Math.max(0, (completedAt ?? Date.now()) - startedAt) : null;
2169
2168
  const summary = isStreaming ? activeLabel : elapsedMs === null ? "Activity" : `Worked for ${formatActivityDuration(elapsedMs)}`;
2170
- const summaryKind = isStreaming ? activeItem?.kind ?? inferActivityKind(activeLabel) : "complete";
2171
2169
  useScrollShadow(panelRef);
2172
2170
  useFollowNewestStep(panelRef, items.length, isStreaming, isExpanded);
2173
2171
  return /* @__PURE__ */ jsxs5(Disclosure2, { className: "ha-activity", isExpanded, onExpandedChange: setIsExpanded, children: [
2174
2172
  /* @__PURE__ */ jsx8(Disclosure2.Heading, { children: /* @__PURE__ */ jsxs5(Disclosure2.Trigger, { className: "ha-activity-trigger", children: [
2175
- /* @__PURE__ */ jsx8(ActivityStatusIcon, { kind: summaryKind }),
2176
2173
  isStreaming ? /* @__PURE__ */ jsx8(
2177
2174
  AnimatedStatusText,
2178
2175
  {
2179
- className: "ha-activity-summary-label ha-progress-shimmer",
2176
+ animateOnMount: summary !== progress,
2177
+ className: "ha-activity-summary-label ha-progress ha-progress-shimmer",
2180
2178
  role: "status",
2181
2179
  children: summary
2182
2180
  }
2183
- ) : /* @__PURE__ */ jsx8("span", { children: summary }),
2181
+ ) : /* @__PURE__ */ jsx8("span", { className: "ha-progress", children: summary }),
2184
2182
  /* @__PURE__ */ jsx8(Disclosure2.Indicator, { className: "ha-activity-chevron" })
2185
2183
  ] }) }),
2186
2184
  /* @__PURE__ */ jsx8(Disclosure2.Content, { className: "ha-activity-disclosure-content", children: /* @__PURE__ */ jsx8(Disclosure2.Body, { children: /* @__PURE__ */ jsx8("div", { ref: panelRef, className: "ha-activity-panel", children: /* @__PURE__ */ jsxs5("ol", { "aria-label": "Thinking steps", children: [
@@ -2224,6 +2222,24 @@ function agentRequestHeaders(token, json = false) {
2224
2222
  "X-HeroUI-Agent-SDK-Version": HEROUI_AGENT_SDK_VERSION
2225
2223
  };
2226
2224
  }
2225
+ async function fetchWithAgentAuthorization(tokenManager, url, init = {}) {
2226
+ const request = async () => {
2227
+ const headers = new Headers(init.headers);
2228
+ headers.set("Authorization", `Bearer ${await tokenManager.get()}`);
2229
+ headers.set("X-HeroUI-Agent-Protocol-Version", String(HEROUI_AGENT_PROTOCOL_VERSION));
2230
+ headers.set("X-HeroUI-Agent-SDK-Version", HEROUI_AGENT_SDK_VERSION);
2231
+ return fetch(url, { ...init, headers });
2232
+ };
2233
+ let response = await request();
2234
+ if (response.status === 401) {
2235
+ tokenManager.clear();
2236
+ response = await request();
2237
+ }
2238
+ return response;
2239
+ }
2240
+ function agentTransportBaseUrl(apiBaseUrl) {
2241
+ return `${apiBaseUrl.replace(/\/$/, "")}/v1/trigger`;
2242
+ }
2227
2243
  async function fetchConversationBootstrap(options, tokenManager, conversationId) {
2228
2244
  for (let attempt = 0; attempt < 2; attempt += 1) {
2229
2245
  const response = await fetch(
@@ -2425,88 +2441,40 @@ function serializeClientTools(tools) {
2425
2441
  );
2426
2442
  }
2427
2443
 
2444
+ // src/embed/component-action-routing.ts
2445
+ function routeComponentPromptAction(action, { fillComposer, setSelection, submitPrompt }) {
2446
+ if (action.type === "followup") {
2447
+ submitPrompt(action.prompt);
2448
+ return true;
2449
+ }
2450
+ if (action.type === "submit") {
2451
+ setSelection({
2452
+ componentId: action.componentId,
2453
+ componentTitle: action.componentTitle,
2454
+ datum: { actionId: action.actionId, values: action.values }
2455
+ });
2456
+ if (action.prompt) fillComposer(action.prompt);
2457
+ return true;
2458
+ }
2459
+ if (action.type === "tool") {
2460
+ fillComposer(action.prompt ?? action.label);
2461
+ return true;
2462
+ }
2463
+ return false;
2464
+ }
2465
+
2428
2466
  // src/embed/component-preview.ts
2429
- var AGENT_UI_KINDS = /* @__PURE__ */ new Set([
2430
- "metric-grid",
2431
- "kpi-grid",
2432
- "line-chart",
2433
- "area-chart",
2434
- "bar-chart",
2435
- "composed-chart",
2436
- "pie-chart",
2437
- "donut-chart",
2438
- "radar-chart",
2439
- "radial-chart",
2440
- "sankey-chart",
2441
- "scatter-chart",
2442
- "heatmap",
2443
- "data-table",
2444
- "comparison-list",
2445
- "meter-list",
2446
- "form",
2447
- "text",
2448
- "callout",
2449
- "channel-message",
2450
- "create-event",
2451
- "enable-notification",
2452
- "event-session",
2453
- "flight-tracker",
2454
- "image",
2455
- "tag-list",
2456
- "list",
2457
- "list-block",
2458
- "map",
2459
- "player-card",
2460
- "playlist",
2461
- "product-signals",
2462
- "purchase-complete",
2463
- "purchase-items",
2464
- "accordion",
2465
- "steps",
2466
- "code-block",
2467
- "tabs",
2468
- "action-group",
2469
- "followup",
2470
- "switch-group",
2471
- "toggle-group",
2472
- "record-card",
2473
- "ride-status",
2474
- "view-event",
2475
- "weather-current",
2476
- "weather-forecast",
2477
- "dashboard",
2478
- "row",
2479
- "col",
2480
- "grid",
2481
- "card",
2482
- "item-card",
2483
- "item-card-group"
2484
- ]);
2485
- function componentPreview(toolName, input) {
2486
- if (!input || typeof input !== "object") return {};
2487
- const request = input;
2488
- const candidate = toolName === "composeUI" && request["component"] && typeof request["component"] === "object" ? request["component"] : request;
2489
- const kind = typeof candidate["kind"] === "string" && AGENT_UI_KINDS.has(candidate["kind"]) ? candidate["kind"] : void 0;
2490
- return {
2491
- kind,
2492
- title: typeof candidate["title"] === "string" ? candidate["title"] : void 0
2493
- };
2467
+ function isRenderToolName(value) {
2468
+ return value === "composeUI" || value === "renderComponent";
2494
2469
  }
2495
- function streamingComponentPreview(part) {
2496
- if (!part || typeof part !== "object") return null;
2470
+ function isStreamingComponentBuild(part) {
2471
+ if (!part || typeof part !== "object") return false;
2497
2472
  const candidate = part;
2498
2473
  const state = candidate["state"];
2499
- if (state !== "input-streaming" && state !== "input-available") return null;
2474
+ if (state !== "input-streaming" && state !== "input-available") return false;
2500
2475
  const type = candidate["type"];
2501
2476
  const toolName = type === "dynamic-tool" ? candidate["toolName"] : typeof type === "string" && type.startsWith("tool-") ? type.slice("tool-".length) : null;
2502
- if (toolName !== "composeUI" && toolName !== "renderComponent") {
2503
- return null;
2504
- }
2505
- const toolCallId = candidate["toolCallId"];
2506
- if (typeof toolCallId !== "string" || !toolCallId) return null;
2507
- const preview = componentPreview(toolName, candidate["input"]);
2508
- if (!preview.kind) return null;
2509
- return { ...preview, kind: preview.kind, toolCallId };
2477
+ return isRenderToolName(toolName);
2510
2478
  }
2511
2479
 
2512
2480
  // src/embed/composer-attachment.tsx
@@ -2866,14 +2834,14 @@ var ConversationMarkdown = memo(function ConversationMarkdown2({
2866
2834
  });
2867
2835
 
2868
2836
  // src/embed/conversation-presence.tsx
2869
- import { useEffect as useEffect7 } from "react";
2837
+ import { useEffect as useEffect8 } from "react";
2870
2838
  function AgentConversationPresence({
2871
2839
  agentId,
2872
2840
  appId,
2873
2841
  conversationId,
2874
2842
  userId
2875
2843
  }) {
2876
- useEffect7(
2844
+ useEffect8(
2877
2845
  () => syncAgentConversationPresence({ agentId, appId, conversationId, userId }),
2878
2846
  [agentId, appId, conversationId, userId]
2879
2847
  );
@@ -2881,6 +2849,7 @@ function AgentConversationPresence({
2881
2849
  }
2882
2850
 
2883
2851
  // src/embed/conversation-presentation.ts
2852
+ var DEFAULT_ACTIVITY_STATUS = "Planning next moves\u2026";
2884
2853
  function getConversationPresentation({
2885
2854
  hasError,
2886
2855
  hasStreamingActivity,
@@ -2896,7 +2865,7 @@ function getConversationPresentation({
2896
2865
  return { showHome, standaloneStatus: null };
2897
2866
  }
2898
2867
  if (isActiveTurn) {
2899
- return { showHome, standaloneStatus: progress ?? "Understanding your request\u2026" };
2868
+ return { showHome, standaloneStatus: progress ?? DEFAULT_ACTIVITY_STATUS };
2900
2869
  }
2901
2870
  if (isAwaitingActiveConversation) {
2902
2871
  return { showHome, standaloneStatus: "Loading chat\u2026" };
@@ -2927,16 +2896,32 @@ function ConversationRouteStack({
2927
2896
  });
2928
2897
  }
2929
2898
 
2899
+ // src/embed/conversation-turn-anchor.ts
2900
+ function resolveConversationTurnAnchor({
2901
+ conversationId,
2902
+ isActive,
2903
+ lastUserMessageId,
2904
+ pendingTurn
2905
+ }) {
2906
+ const normalizedLastUserMessageId = lastUserMessageId ?? null;
2907
+ const hasActiveOptimisticUser = pendingTurn ? normalizedLastUserMessageId !== pendingTurn.previousUserMessageId : normalizedLastUserMessageId !== null;
2908
+ const activeTurnMessageId = isActive ? hasActiveOptimisticUser ? normalizedLastUserMessageId : null : normalizedLastUserMessageId;
2909
+ const activeStreamKey = isActive ? pendingTurn?.messageId ?? activeTurnMessageId ?? conversationId : null;
2910
+ return { activeStreamKey, activeTurnMessageId };
2911
+ }
2912
+
2930
2913
  // src/embed/conversation-viewport.tsx
2931
2914
  import { ChevronDown } from "@gravity-ui/icons";
2932
2915
  import { Button } from "@heroui/react";
2933
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef7 } from "react";
2916
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef8 } from "react";
2934
2917
 
2935
2918
  // src/embed/use-conversation-scroll.ts
2936
- import { useCallback as useCallback5, useEffect as useEffect8, useLayoutEffect, useRef as useRef6, useState as useState6 } from "react";
2919
+ import { useCallback as useCallback5, useEffect as useEffect9, useLayoutEffect, useRef as useRef7, useState as useState6 } from "react";
2937
2920
  var BOTTOM_THRESHOLD = 4;
2938
2921
  var SCROLL_DIRECTION_THRESHOLD = 1;
2922
+ var ACTIVE_TURN_SPACER_PROPERTY = "--ha-active-turn-spacer-height";
2939
2923
  var STREAM_ANCHOR_SELECTOR = '[data-stream-anchor="true"]';
2924
+ var TURN_ANCHOR_SELECTOR = '[data-turn-anchor="true"]';
2940
2925
  var DEFAULT_SCROLL_STATE = {
2941
2926
  hasOverflow: false,
2942
2927
  isAtBottom: true
@@ -2946,6 +2931,15 @@ function getConversationScrollState(element) {
2946
2931
  const isAtBottom = !hasOverflow || element.scrollHeight - Math.max(0, element.scrollTop) - element.clientHeight <= BOTTOM_THRESHOLD;
2947
2932
  return { hasOverflow, isAtBottom };
2948
2933
  }
2934
+ function getActiveTurnSpacerHeight({
2935
+ anchorTop,
2936
+ clientHeight,
2937
+ contentHeight,
2938
+ paddingTop
2939
+ }) {
2940
+ const turnHeight = Math.max(0, contentHeight - anchorTop);
2941
+ return Math.min(Math.max(clientHeight - turnHeight - paddingTop, 0), clientHeight);
2942
+ }
2949
2943
  function isSameScrollState(currentState, nextState) {
2950
2944
  return currentState.hasOverflow === nextState.hasOverflow && currentState.isAtBottom === nextState.isAtBottom;
2951
2945
  }
@@ -2956,24 +2950,51 @@ function getNativeScrollBehavior(behavior) {
2956
2950
  return "auto";
2957
2951
  }
2958
2952
  function useConversationScroll(ref) {
2959
- const attachedRef = useRef6(true);
2960
- const currentStreamKeyRef = useRef6(null);
2961
- const isAutoScrollingRef = useRef6(false);
2962
- const lastScrollTopRef = useRef6(0);
2963
- const pinnedStreamKeyRef = useRef6(null);
2964
- const pinnedStreamTopRef = useRef6(null);
2953
+ const activeTurnSpacerHeightRef = useRef7(0);
2954
+ const attachedRef = useRef7(true);
2955
+ const currentStreamKeyRef = useRef7(null);
2956
+ const isAutoScrollingRef = useRef7(false);
2957
+ const isPinnedTurnAnimatingRef = useRef7(false);
2958
+ const lastScrollTopRef = useRef7(0);
2959
+ const pinnedStreamKeyRef = useRef7(null);
2960
+ const pinnedStreamTopRef = useRef7(null);
2961
+ const shouldAnimatePinnedTurnRef = useRef7(false);
2965
2962
  const [scrollState, setScrollState] = useState6(DEFAULT_SCROLL_STATE);
2966
2963
  const setNextScrollState = useCallback5((nextState) => {
2967
2964
  setScrollState(
2968
2965
  (currentState) => isSameScrollState(currentState, nextState) ? currentState : nextState
2969
2966
  );
2970
2967
  }, []);
2968
+ const updateActiveTurnSpacer = useCallback5((element) => {
2969
+ const anchor = element.querySelector(STREAM_ANCHOR_SELECTOR) ?? element.querySelector(TURN_ANCHOR_SELECTOR);
2970
+ let nextHeight = 0;
2971
+ if (anchor) {
2972
+ const containerRect = element.getBoundingClientRect();
2973
+ const anchorRect = anchor.getBoundingClientRect();
2974
+ const anchorTop = anchorRect.top - containerRect.top + element.scrollTop;
2975
+ const contentHeight = element.scrollHeight - activeTurnSpacerHeightRef.current;
2976
+ const paddingTop = Number.parseFloat(window.getComputedStyle(element).paddingTop) || 0;
2977
+ nextHeight = getActiveTurnSpacerHeight({
2978
+ anchorTop,
2979
+ clientHeight: element.clientHeight,
2980
+ contentHeight,
2981
+ paddingTop
2982
+ });
2983
+ }
2984
+ if (Math.abs(nextHeight - activeTurnSpacerHeightRef.current) > 1) {
2985
+ activeTurnSpacerHeightRef.current = nextHeight;
2986
+ element.style.setProperty(ACTIVE_TURN_SPACER_PROPERTY, `${nextHeight}px`);
2987
+ }
2988
+ return activeTurnSpacerHeightRef.current;
2989
+ }, []);
2971
2990
  const scrollToBottom = useCallback5(
2972
2991
  (behavior = "smooth") => {
2973
2992
  const element = ref.current;
2974
2993
  if (!element) return;
2975
2994
  pinnedStreamKeyRef.current = null;
2976
2995
  pinnedStreamTopRef.current = null;
2996
+ isPinnedTurnAnimatingRef.current = false;
2997
+ shouldAnimatePinnedTurnRef.current = false;
2977
2998
  attachedRef.current = true;
2978
2999
  isAutoScrollingRef.current = true;
2979
3000
  lastScrollTopRef.current = element.scrollTop;
@@ -3001,9 +3022,11 @@ function useConversationScroll(ref) {
3001
3022
  return pinnedStreamTopRef.current;
3002
3023
  }, []);
3003
3024
  const scrollToPinnedPosition = useCallback5(
3004
- (element, top) => {
3025
+ (element, top, behavior = "auto") => {
3005
3026
  const maximumScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
3006
3027
  const targetTop = Math.min(top, maximumScrollTop);
3028
+ const nativeBehavior = getNativeScrollBehavior(behavior);
3029
+ isPinnedTurnAnimatingRef.current = nativeBehavior === "smooth" && Math.abs(Math.max(0, element.scrollTop) - targetTop) > SCROLL_DIRECTION_THRESHOLD;
3007
3030
  isAutoScrollingRef.current = true;
3008
3031
  lastScrollTopRef.current = element.scrollTop;
3009
3032
  setNextScrollState({
@@ -3011,9 +3034,10 @@ function useConversationScroll(ref) {
3011
3034
  isAtBottom: maximumScrollTop - targetTop <= BOTTOM_THRESHOLD
3012
3035
  });
3013
3036
  if (typeof element.scrollTo === "function") {
3014
- element.scrollTo({ behavior: "auto", top });
3037
+ element.scrollTo({ behavior: nativeBehavior, top });
3015
3038
  } else {
3016
3039
  element.scrollTop = top;
3040
+ isPinnedTurnAnimatingRef.current = false;
3017
3041
  }
3018
3042
  lastScrollTopRef.current = Math.max(0, element.scrollTop);
3019
3043
  },
@@ -3022,6 +3046,7 @@ function useConversationScroll(ref) {
3022
3046
  const maintainScrollPosition = useCallback5(() => {
3023
3047
  const element = ref.current;
3024
3048
  if (!element) return;
3049
+ const activeTurnSpacerHeight = updateActiveTurnSpacer(element);
3025
3050
  const measuredState = getConversationScrollState(element);
3026
3051
  const scrollTop = Math.max(0, element.scrollTop);
3027
3052
  const scrolledUp = scrollTop < lastScrollTopRef.current - SCROLL_DIRECTION_THRESHOLD;
@@ -3029,8 +3054,10 @@ function useConversationScroll(ref) {
3029
3054
  if (scrolledUp && !measuredState.isAtBottom) {
3030
3055
  attachedRef.current = false;
3031
3056
  isAutoScrollingRef.current = false;
3057
+ isPinnedTurnAnimatingRef.current = false;
3032
3058
  pinnedStreamKeyRef.current = null;
3033
3059
  pinnedStreamTopRef.current = null;
3060
+ shouldAnimatePinnedTurnRef.current = false;
3034
3061
  setNextScrollState(measuredState);
3035
3062
  return;
3036
3063
  }
@@ -3039,15 +3066,34 @@ function useConversationScroll(ref) {
3039
3066
  }
3040
3067
  if (attachedRef.current) {
3041
3068
  const pinnedStreamTop = getPinnedStreamTop(element);
3042
- if (pinnedStreamTop !== null) {
3043
- scrollToPinnedPosition(element, pinnedStreamTop);
3069
+ if (pinnedStreamTop !== null && isPinnedTurnAnimatingRef.current) {
3070
+ const maximumScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
3071
+ const targetTop = Math.min(pinnedStreamTop, maximumScrollTop);
3072
+ if (Math.abs(scrollTop - targetTop) > SCROLL_DIRECTION_THRESHOLD) {
3073
+ setNextScrollState({ hasOverflow: measuredState.hasOverflow, isAtBottom: true });
3074
+ return;
3075
+ }
3076
+ isAutoScrollingRef.current = false;
3077
+ isPinnedTurnAnimatingRef.current = false;
3078
+ }
3079
+ if (pinnedStreamTop !== null && activeTurnSpacerHeight > BOTTOM_THRESHOLD) {
3080
+ const behavior = shouldAnimatePinnedTurnRef.current ? "smooth" : "auto";
3081
+ shouldAnimatePinnedTurnRef.current = false;
3082
+ scrollToPinnedPosition(element, pinnedStreamTop, behavior);
3044
3083
  return;
3045
3084
  }
3046
3085
  scrollToBottom("auto");
3047
3086
  return;
3048
3087
  }
3049
3088
  setNextScrollState({ hasOverflow: measuredState.hasOverflow, isAtBottom: false });
3050
- }, [getPinnedStreamTop, ref, scrollToBottom, scrollToPinnedPosition, setNextScrollState]);
3089
+ }, [
3090
+ getPinnedStreamTop,
3091
+ ref,
3092
+ scrollToBottom,
3093
+ scrollToPinnedPosition,
3094
+ setNextScrollState,
3095
+ updateActiveTurnSpacer
3096
+ ]);
3051
3097
  const setPinnedStream = useCallback5(
3052
3098
  (streamKey) => {
3053
3099
  const element = ref.current;
@@ -3056,10 +3102,15 @@ function useConversationScroll(ref) {
3056
3102
  currentStreamKeyRef.current = streamKey;
3057
3103
  pinnedStreamKeyRef.current = streamKey;
3058
3104
  pinnedStreamTopRef.current = null;
3059
- isAutoScrollingRef.current = false;
3060
3105
  if (streamKey) {
3106
+ if (!hadActiveStream) {
3107
+ shouldAnimatePinnedTurnRef.current = true;
3108
+ }
3061
3109
  attachedRef.current = true;
3062
3110
  } else if (hadActiveStream) {
3111
+ isAutoScrollingRef.current = false;
3112
+ isPinnedTurnAnimatingRef.current = false;
3113
+ shouldAnimatePinnedTurnRef.current = false;
3063
3114
  const measuredState = getConversationScrollState(element);
3064
3115
  attachedRef.current = measuredState.isAtBottom;
3065
3116
  setNextScrollState(measuredState);
@@ -3073,7 +3124,7 @@ function useConversationScroll(ref) {
3073
3124
  lastScrollTopRef.current = element.scrollTop;
3074
3125
  maintainScrollPosition();
3075
3126
  }, [maintainScrollPosition, ref]);
3076
- useEffect8(() => {
3127
+ useEffect9(() => {
3077
3128
  const element = ref.current;
3078
3129
  if (!element) return;
3079
3130
  const handleScroll = () => {
@@ -3085,17 +3136,22 @@ function useConversationScroll(ref) {
3085
3136
  const pinnedStreamTop = getPinnedStreamTop(element);
3086
3137
  const maximumScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
3087
3138
  const automaticTarget = Math.min(pinnedStreamTop ?? maximumScrollTop, maximumScrollTop);
3088
- if (measuredState.isAtBottom || Math.abs(scrollTop - automaticTarget) <= SCROLL_DIRECTION_THRESHOLD) {
3139
+ const reachedAutomaticTarget = measuredState.isAtBottom || Math.abs(scrollTop - automaticTarget) <= SCROLL_DIRECTION_THRESHOLD;
3140
+ const wasPinnedTurnAnimating = isPinnedTurnAnimatingRef.current;
3141
+ if (reachedAutomaticTarget) {
3089
3142
  isAutoScrollingRef.current = false;
3143
+ isPinnedTurnAnimatingRef.current = false;
3090
3144
  }
3091
3145
  setNextScrollState(
3092
- pinnedStreamTop === null ? { hasOverflow: measuredState.hasOverflow, isAtBottom: attachedRef.current } : measuredState
3146
+ wasPinnedTurnAnimating && !reachedAutomaticTarget ? { hasOverflow: measuredState.hasOverflow, isAtBottom: true } : pinnedStreamTop === null ? { hasOverflow: measuredState.hasOverflow, isAtBottom: attachedRef.current } : measuredState
3093
3147
  );
3094
3148
  return;
3095
3149
  }
3096
3150
  isAutoScrollingRef.current = false;
3151
+ isPinnedTurnAnimatingRef.current = false;
3097
3152
  pinnedStreamKeyRef.current = null;
3098
3153
  pinnedStreamTopRef.current = null;
3154
+ shouldAnimatePinnedTurnRef.current = false;
3099
3155
  attachedRef.current = measuredState.isAtBottom;
3100
3156
  setNextScrollState(measuredState);
3101
3157
  };
@@ -3132,8 +3188,12 @@ function useConversationScroll(ref) {
3132
3188
  mutationObserver?.observe(element, { characterData: true, childList: true, subtree: true });
3133
3189
  return () => {
3134
3190
  element.removeEventListener("scroll", handleScroll);
3135
- mutationObserver?.disconnect();
3136
- resizeObserver?.disconnect();
3191
+ if (mutationObserver) {
3192
+ mutationObserver.disconnect();
3193
+ }
3194
+ if (resizeObserver) {
3195
+ resizeObserver.disconnect();
3196
+ }
3137
3197
  };
3138
3198
  }, [getPinnedStreamTop, maintainScrollPosition, ref, setNextScrollState]);
3139
3199
  return {
@@ -3151,7 +3211,7 @@ function ConversationViewport({
3151
3211
  children,
3152
3212
  streamKey = null
3153
3213
  }) {
3154
- const scrollRef = useRef7(null);
3214
+ const scrollRef = useRef8(null);
3155
3215
  const { hasOverflow, isAtBottom, maintainScrollPosition, scrollToBottom, setPinnedStream } = useConversationScroll(scrollRef);
3156
3216
  const isScrollButtonVisible = hasOverflow && !isAtBottom;
3157
3217
  useScrollShadow(scrollRef);
@@ -3188,32 +3248,32 @@ function ConversationViewport({
3188
3248
 
3189
3249
  // src/embed/genui-renderer.tsx
3190
3250
  import { Suspense as Suspense2, lazy as lazy2 } from "react";
3251
+
3252
+ // src/embed/genui-renderer-loader.ts
3253
+ var componentRendererPromise;
3254
+ function loadGenUIRenderer() {
3255
+ componentRendererPromise ??= import("./component-renderer-PTMCOJHE.js").then(
3256
+ (module) => ({ default: module.ComponentRenderer })
3257
+ );
3258
+ return componentRendererPromise;
3259
+ }
3260
+ function preloadGenUIRenderer() {
3261
+ void loadGenUIRenderer().catch(() => {
3262
+ componentRendererPromise = void 0;
3263
+ });
3264
+ }
3265
+
3266
+ // src/embed/genui-renderer.tsx
3191
3267
  import { jsx as jsx15 } from "react/jsx-runtime";
3192
- var LazyComponentRenderer = lazy2(
3193
- () => import("./component-renderer-EOOT6ONY.js").then((module) => ({
3194
- default: module.ComponentRenderer
3195
- }))
3196
- );
3268
+ var LazyComponentRenderer = lazy2(loadGenUIRenderer);
3197
3269
  function GenUIRenderer(props) {
3198
- return /* @__PURE__ */ jsx15(
3199
- Suspense2,
3200
- {
3201
- fallback: /* @__PURE__ */ jsx15(
3202
- ComponentSkeleton,
3203
- {
3204
- className: props.className,
3205
- kind: props.component && typeof props.component === "object" && "kind" in props.component ? props.component.kind : void 0
3206
- }
3207
- ),
3208
- children: /* @__PURE__ */ jsx15(LazyComponentRenderer, { ...props })
3209
- }
3210
- );
3270
+ return /* @__PURE__ */ jsx15(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx15(LazyComponentRenderer, { ...props }) });
3211
3271
  }
3212
3272
 
3213
3273
  // src/embed/message-actions.tsx
3214
3274
  import { ArrowsRotateLeft, Check, Copy, ThumbsDown, ThumbsUp } from "@gravity-ui/icons";
3215
3275
  import { Button as Button2, Popover, TextArea } from "@heroui/react";
3216
- import { useCallback as useCallback6, useEffect as useEffect9, useRef as useRef8, useState as useState7 } from "react";
3276
+ import { useCallback as useCallback6, useEffect as useEffect10, useRef as useRef9, useState as useState7 } from "react";
3217
3277
  import { Fragment, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
3218
3278
  var COPY_RESET_DELAY = 2e3;
3219
3279
  var FEEDBACK_COMMENT_MAX_LENGTH = 1e3;
@@ -3237,12 +3297,12 @@ function MessageActions({
3237
3297
  const [feedbackComment, setFeedbackComment] = useState7("");
3238
3298
  const [feedbackReasons, setFeedbackReasons] = useState7([]);
3239
3299
  const [feedbackPortalContainer, setFeedbackPortalContainer] = useState7();
3240
- const resetTimeoutRef = useRef8(null);
3241
- const feedbackOpenedAtRef = useRef8(0);
3300
+ const resetTimeoutRef = useRef9(null);
3301
+ const feedbackOpenedAtRef = useRef9(0);
3242
3302
  const feedbackTriggerRef = useCallback6((node) => {
3243
3303
  setFeedbackPortalContainer(node?.closest(".ha-panel") ?? void 0);
3244
3304
  }, []);
3245
- useEffect9(() => {
3305
+ useEffect10(() => {
3246
3306
  return () => {
3247
3307
  if (resetTimeoutRef.current !== null) window.clearTimeout(resetTimeoutRef.current);
3248
3308
  };
@@ -3504,6 +3564,13 @@ function shouldQueueAgentMessage({
3504
3564
  }
3505
3565
 
3506
3566
  // src/embed/message-queue-coordinator.ts
3567
+ var AGENT_SESSION_START_ERROR_MESSAGE = "Could not start the assistant session";
3568
+ var AgentSessionStartError = class extends Error {
3569
+ constructor(options) {
3570
+ super(AGENT_SESSION_START_ERROR_MESSAGE, options);
3571
+ this.name = "AgentSessionStartError";
3572
+ }
3573
+ };
3507
3574
  function getAgentTransportModelId({
3508
3575
  activeTurnModelId,
3509
3576
  defaultModelId,
@@ -3536,6 +3603,9 @@ function getAgentMessageFailureAction({
3536
3603
  if (sendAcknowledged || transportFailure === "stream-error") return "reconcile";
3537
3604
  return "requeue";
3538
3605
  }
3606
+ function shouldRequeueAgentMessage(error) {
3607
+ return !(error instanceof AgentSessionStartError);
3608
+ }
3539
3609
  function shouldReconcileStalledAgentTurn({
3540
3610
  isLocalPending,
3541
3611
  isReconciling,
@@ -3574,19 +3644,19 @@ function handleMessageQueueComposerKeyDown(event, queue) {
3574
3644
 
3575
3645
  // src/embed/message-queue-list.tsx
3576
3646
  import { ArrowUp, CircleExclamation, Clock, Grip, Pencil, TrashBin } from "@gravity-ui/icons";
3577
- import { useImperativeHandle, useLayoutEffect as useLayoutEffect3, useRef as useRef10 } from "react";
3647
+ import { useImperativeHandle, useLayoutEffect as useLayoutEffect3, useRef as useRef11 } from "react";
3578
3648
 
3579
3649
  // src/embed/use-message-queue-reorder.ts
3580
- import { useRef as useRef9, useState as useState8 } from "react";
3650
+ import { useRef as useRef10, useState as useState8 } from "react";
3581
3651
  function useMessageQueueReorder({
3582
3652
  messages,
3583
3653
  onReorder,
3584
3654
  onReorderStateChange
3585
3655
  }) {
3586
- const dragStateRef = useRef9(null);
3587
- const dropTargetRef = useRef9(null);
3588
- const positionPickerMessageIdRef = useRef9(null);
3589
- const suppressedClickMessageIdRef = useRef9(null);
3656
+ const dragStateRef = useRef10(null);
3657
+ const dropTargetRef = useRef10(null);
3658
+ const positionPickerMessageIdRef = useRef10(null);
3659
+ const suppressedClickMessageIdRef = useRef10(null);
3590
3660
  const [announcement, setAnnouncement] = useState8("");
3591
3661
  const [draggedMessageId, setDraggedMessageId] = useState8(null);
3592
3662
  const [dropTarget, setDropTarget] = useState8(null);
@@ -3758,9 +3828,9 @@ function MessageQueueList({
3758
3828
  onSendNow,
3759
3829
  ref
3760
3830
  }) {
3761
- const focusedMessageIdRef = useRef10(null);
3762
- const listRef = useRef10(null);
3763
- const previousMessagesRef = useRef10(messages);
3831
+ const focusedMessageIdRef = useRef11(null);
3832
+ const listRef = useRef11(null);
3833
+ const previousMessagesRef = useRef11(messages);
3764
3834
  const {
3765
3835
  announcement,
3766
3836
  closePositionPicker,
@@ -3925,7 +3995,7 @@ import { composeRenderProps as composeRenderProps2 } from "react-aria-components
3925
3995
  // ../agent-ui/src/components/hover-card/hover-card.tsx
3926
3996
  import { mergeRefs } from "@react-aria/utils";
3927
3997
  import { useControlledState } from "@react-stately/utils";
3928
- import { createContext, useCallback as useCallback7, useContext, useEffect as useEffect10, useMemo as useMemo2, useRef as useRef11, useState as useState9 } from "react";
3998
+ import { createContext, useCallback as useCallback7, useContext, useEffect as useEffect11, useMemo as useMemo2, useRef as useRef12, useState as useState9 } from "react";
3929
3999
  import {
3930
4000
  OverlayArrow as OverlayArrowPrimitive,
3931
4001
  Popover as PopoverPrimitive,
@@ -3955,16 +4025,16 @@ var HoverCardRoot = ({
3955
4025
  }) => {
3956
4026
  const [isOpen, setOpen] = useControlledState(open, defaultOpen, onOpenChange);
3957
4027
  const [portalContainer, setPortalContainer] = useState9();
3958
- const triggerRef = useRef11(null);
3959
- const isPointerInsideRef = useRef11(false);
3960
- const openTimerRef = useRef11(void 0);
3961
- const closeTimerRef = useRef11(void 0);
4028
+ const triggerRef = useRef12(null);
4029
+ const isPointerInsideRef = useRef12(false);
4030
+ const openTimerRef = useRef12(void 0);
4031
+ const closeTimerRef = useRef12(void 0);
3962
4032
  const clearTimers = useCallback7(() => {
3963
4033
  clearTimeout(openTimerRef.current);
3964
4034
  clearTimeout(closeTimerRef.current);
3965
4035
  }, []);
3966
- useEffect10(() => clearTimers, [clearTimers]);
3967
- useEffect10(() => {
4036
+ useEffect11(() => clearTimers, [clearTimers]);
4037
+ useEffect11(() => {
3968
4038
  if (!isOpen) return;
3969
4039
  const onKeyDown = (event) => {
3970
4040
  if (event.key === "Escape") {
@@ -4594,7 +4664,7 @@ function writeCachedProjectConfig(agentId, config) {
4594
4664
  // src/embed/session-picker.tsx
4595
4665
  import { ChevronDown as ChevronDown2, Ellipsis, Pencil as Pencil2, Plus as Plus2, TrashBin as TrashBin2 } from "@gravity-ui/icons";
4596
4666
  import { Button as Button4, Dropdown, Input, Label as Label3, Modal, Popover as Popover2, TextField } from "@heroui/react";
4597
- import { useCallback as useCallback8, useRef as useRef12, useState as useState10 } from "react";
4667
+ import { useCallback as useCallback8, useRef as useRef13, useState as useState10 } from "react";
4598
4668
  import { Fragment as Fragment3, jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
4599
4669
  var RENAME_KEY = "rename";
4600
4670
  var DELETE_KEY = "delete";
@@ -4619,9 +4689,9 @@ function SessionPicker({
4619
4689
  const [mutationError, setMutationError] = useState10(null);
4620
4690
  const [isPending, setIsPending] = useState10(false);
4621
4691
  const [portalContainer, setPortalContainer] = useState10();
4622
- const pointerOpenAt = useRef12(0);
4623
- const actionPointerOpenAt = useRef12(0);
4624
- const sidebarHistoryRef = useRef12(null);
4692
+ const pointerOpenAt = useRef13(0);
4693
+ const actionPointerOpenAt = useRef13(0);
4694
+ const sidebarHistoryRef = useRef13(null);
4625
4695
  useScrollShadow(sidebarHistoryRef);
4626
4696
  const activeConversation = groups.flatMap((group) => group.items).find((conversation) => conversation.id === activeConversationId);
4627
4697
  const activeTitle = activeConversation?.title ?? "New chat";
@@ -4983,6 +5053,39 @@ function SessionDialogError({ message }) {
4983
5053
  return message ? /* @__PURE__ */ jsx24("p", { className: "ha-session-modal-error", role: "alert", children: message }) : null;
4984
5054
  }
4985
5055
 
5056
+ // src/embed/startup-activity-status.tsx
5057
+ import { useEffect as useEffect12, useState as useState11 } from "react";
5058
+ import { jsx as jsx25 } from "react/jsx-runtime";
5059
+ var STARTUP_ACTIVITY_STATUSES = [
5060
+ "Thinking\u2026",
5061
+ "Analyzing request\u2026",
5062
+ "Preparing next steps\u2026"
5063
+ ];
5064
+ var STARTUP_ACTIVITY_STATUS_INTERVAL_MS = 1600;
5065
+ function StartupActivityStatus() {
5066
+ const [statusIndex, setStatusIndex] = useState11(0);
5067
+ useEffect12(() => {
5068
+ const timers = STARTUP_ACTIVITY_STATUSES.slice(1).map(
5069
+ (_, index) => window.setTimeout(
5070
+ () => setStatusIndex(index + 1),
5071
+ STARTUP_ACTIVITY_STATUS_INTERVAL_MS * (index + 1)
5072
+ )
5073
+ );
5074
+ return () => {
5075
+ for (const timer of timers) {
5076
+ window.clearTimeout(timer);
5077
+ }
5078
+ };
5079
+ }, []);
5080
+ return /* @__PURE__ */ jsx25(
5081
+ ActivityStatus,
5082
+ {
5083
+ ariaLabel: "Working on your request\u2026",
5084
+ label: STARTUP_ACTIVITY_STATUSES[statusIndex] ?? STARTUP_ACTIVITY_STATUSES[0]
5085
+ }
5086
+ );
5087
+ }
5088
+
4986
5089
  // src/embed/trigger-session.ts
4987
5090
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["pending", "streaming"]);
4988
5091
  function restoreTriggerSession(stored, bootstrapStatus) {
@@ -5003,7 +5106,7 @@ function restoreTriggerSession(stored, bootstrapStatus) {
5003
5106
  }
5004
5107
 
5005
5108
  // src/embed/use-composer-draft.ts
5006
- import { useCallback as useCallback9, useEffect as useEffect11, useRef as useRef13, useState as useState11 } from "react";
5109
+ import { useCallback as useCallback9, useEffect as useEffect13, useRef as useRef14, useState as useState12 } from "react";
5007
5110
  var AGENT_COMPOSER_DRAFT_SAVE_DEBOUNCE_MS = 300;
5008
5111
  function useComposerDraft({
5009
5112
  attachments,
@@ -5012,14 +5115,14 @@ function useComposerDraft({
5012
5115
  setAttachments,
5013
5116
  setInput
5014
5117
  }) {
5015
- const [restoredImageDraftKey, setRestoredImageDraftKey] = useState11(null);
5016
- const [restoredPromptDraftKey, setRestoredPromptDraftKey] = useState11(null);
5017
- const latestInputRef = useRef13(input);
5018
- const saveTimeoutRef = useRef13(null);
5019
- useEffect11(() => {
5118
+ const [restoredImageDraftKey, setRestoredImageDraftKey] = useState12(null);
5119
+ const [restoredPromptDraftKey, setRestoredPromptDraftKey] = useState12(null);
5120
+ const latestInputRef = useRef14(input);
5121
+ const saveTimeoutRef = useRef14(null);
5122
+ useEffect13(() => {
5020
5123
  latestInputRef.current = input;
5021
5124
  }, [input]);
5022
- useEffect11(() => {
5125
+ useEffect13(() => {
5023
5126
  let cancelled = false;
5024
5127
  const savedPrompt = readAgentComposerPromptDraft(draftKey);
5025
5128
  queueMicrotask(() => {
@@ -5038,7 +5141,7 @@ function useComposerDraft({
5038
5141
  cancelled = true;
5039
5142
  };
5040
5143
  }, [draftKey, setAttachments, setInput]);
5041
- useEffect11(() => {
5144
+ useEffect13(() => {
5042
5145
  if (restoredPromptDraftKey !== draftKey) return;
5043
5146
  const flush = () => saveAgentComposerPromptDraft(draftKey, input);
5044
5147
  saveTimeoutRef.current = window.setTimeout(flush, AGENT_COMPOSER_DRAFT_SAVE_DEBOUNCE_MS);
@@ -5049,11 +5152,11 @@ function useComposerDraft({
5049
5152
  window.removeEventListener("pagehide", flush);
5050
5153
  };
5051
5154
  }, [draftKey, input, restoredPromptDraftKey]);
5052
- useEffect11(() => {
5155
+ useEffect13(() => {
5053
5156
  if (restoredPromptDraftKey !== draftKey) return;
5054
5157
  return () => saveAgentComposerPromptDraft(draftKey, latestInputRef.current);
5055
5158
  }, [draftKey, restoredPromptDraftKey]);
5056
- useEffect11(() => {
5159
+ useEffect13(() => {
5057
5160
  if (restoredImageDraftKey !== draftKey) return;
5058
5161
  void saveAgentComposerImageDraft(draftKey, attachments);
5059
5162
  }, [attachments, draftKey, restoredImageDraftKey]);
@@ -5066,7 +5169,7 @@ function useComposerDraft({
5066
5169
  }
5067
5170
 
5068
5171
  // src/embed/use-persisted-model-selection.ts
5069
- import { useCallback as useCallback10, useState as useState12 } from "react";
5172
+ import { useCallback as useCallback10, useState as useState13 } from "react";
5070
5173
  function agentModelPreferenceStorageKey(identityStorageKey) {
5071
5174
  return `${identityStorageKey}:model`;
5072
5175
  }
@@ -5094,7 +5197,7 @@ function storeModelPreference(identityStorageKey, modelId) {
5094
5197
  }
5095
5198
  }
5096
5199
  function usePersistedModelSelection(identityStorageKey, configuredDefaultModelId) {
5097
- const [preferredModelId, setPreferredModelId] = useState12(
5200
+ const [preferredModelId, setPreferredModelId] = useState13(
5098
5201
  () => readStoredModelPreference(identityStorageKey)
5099
5202
  );
5100
5203
  const selectedModelId = preferredModelId ?? configuredDefaultModelId ?? DEFAULT_AGENT_PICKER_MODEL_ID;
@@ -5209,8 +5312,7 @@ async function transcribeVoiceRecording({
5209
5312
  }
5210
5313
 
5211
5314
  // src/embed/embed-runtime.tsx
5212
- import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
5213
- var DEFAULT_TRIGGER_API_URL = "https://api.trigger.dev";
5315
+ import { Fragment as Fragment4, jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
5214
5316
  var TRANSPORT_COMPLETION_WATCHDOG_MS = 3e4;
5215
5317
  function createReconciliationDeferred() {
5216
5318
  let resolve;
@@ -5253,14 +5355,14 @@ function EmbedRuntime({
5253
5355
  () => new EmbedSessionManager(options.agentId, options.getAuthToken),
5254
5356
  [options.getAuthToken, options.agentId]
5255
5357
  );
5256
- const [activeConversationId, setActiveConversationId] = useState13(null);
5257
- const [runtimeRoutes, setRuntimeRoutes] = useState13({});
5258
- const runtimeRoutesRef = useRef14(runtimeRoutes);
5259
- const activeConversationIdRef = useRef14(activeConversationId);
5260
- const [error, setError] = useState13(null);
5261
- const [history, setHistory] = useState13([]);
5262
- const [conversationActivity, setConversationActivity] = useState13(createConversationActivityState);
5263
- const [messageQueues, setMessageQueues] = useState13({});
5358
+ const [activeConversationId, setActiveConversationId] = useState14(null);
5359
+ const [runtimeRoutes, setRuntimeRoutes] = useState14({});
5360
+ const runtimeRoutesRef = useRef15(runtimeRoutes);
5361
+ const activeConversationIdRef = useRef15(activeConversationId);
5362
+ const [error, setError] = useState14(null);
5363
+ const [history, setHistory] = useState14([]);
5364
+ const [conversationActivity, setConversationActivity] = useState14(createConversationActivityState);
5365
+ const [messageQueues, setMessageQueues] = useState14({});
5264
5366
  const runtime = activeConversationId ? runtimeRoutes[activeConversationId] ?? null : null;
5265
5367
  useLayoutEffect4(() => {
5266
5368
  runtimeRoutesRef.current = runtimeRoutes;
@@ -5303,7 +5405,7 @@ function EmbedRuntime({
5303
5405
  },
5304
5406
  []
5305
5407
  );
5306
- const bootstrapCacheRef = useRef14(null);
5408
+ const bootstrapCacheRef = useRef15(null);
5307
5409
  const apiBaseUrl = options.apiBaseUrl;
5308
5410
  const agentId = options.agentId;
5309
5411
  const notifyReady = useEffectEvent(onReady);
@@ -5321,7 +5423,7 @@ function EmbedRuntime({
5321
5423
  const reportEmbedLoaded = useEffectEvent((conversationId) => {
5322
5424
  void reportEvent(options, tokenManager, conversationId, "embed_loaded");
5323
5425
  });
5324
- const appliedIdentityEpochRef = useRef14(identityEpoch);
5426
+ const appliedIdentityEpochRef = useRef15(identityEpoch);
5325
5427
  useLayoutEffect4(() => {
5326
5428
  if (appliedIdentityEpochRef.current === identityEpoch) return;
5327
5429
  appliedIdentityEpochRef.current = identityEpoch;
@@ -5336,7 +5438,7 @@ function EmbedRuntime({
5336
5438
  if (identityReset) tokenManager.reset();
5337
5439
  else tokenManager.clear();
5338
5440
  }, [identityEpoch, identityReset, tokenManager]);
5339
- useEffect12(() => {
5441
+ useEffect14(() => {
5340
5442
  let cancelled = false;
5341
5443
  if (clientToolValidationError) {
5342
5444
  setError(clientToolValidationError);
@@ -5482,12 +5584,12 @@ function EmbedRuntime({
5482
5584
  ),
5483
5585
  [clientToolValidationError, options.permissionDefaultMode, options.tools]
5484
5586
  );
5485
- const preloadedConversationIds = useRef14(/* @__PURE__ */ new Set());
5587
+ const preloadedConversationIds = useRef15(/* @__PURE__ */ new Set());
5486
5588
  const warmAgentSession = useEffectEvent((conversationId) => {
5487
5589
  preloadedConversationIds.current.add(conversationId);
5488
5590
  void preloadAgentSession(options, tokenManager, conversationId, preloadClientTools);
5489
5591
  });
5490
- useEffect12(() => {
5592
+ useEffect14(() => {
5491
5593
  if (!warmSession && !open) return;
5492
5594
  const conversationId = runtime?.conversationId;
5493
5595
  if (!conversationId || preloadedConversationIds.current.has(conversationId)) return;
@@ -5598,8 +5700,8 @@ function EmbedRuntime({
5598
5700
  );
5599
5701
  }, [options.suggestedPrompts, runtimeRoutes]);
5600
5702
  const runtimeConfig = activeConversationId ? configuredRuntimeRoutes[activeConversationId]?.config ?? null : null;
5601
- const openedConversationRef = useRef14(null);
5602
- useEffect12(() => {
5703
+ const openedConversationRef = useRef15(null);
5704
+ useEffect14(() => {
5603
5705
  if (!open || !runtime || openedConversationRef.current === runtime.conversationId) return;
5604
5706
  openedConversationRef.current = runtime.conversationId;
5605
5707
  void reportEvent(options, tokenManager, runtime.conversationId, "opened");
@@ -5609,7 +5711,7 @@ function EmbedRuntime({
5609
5711
  const activeConversationTitle = history.find((conversation) => conversation.id === activeConversationId)?.title ?? "New chat";
5610
5712
  const loadingMessage = bootstrapCacheRef.current ? "Loading chat\u2026" : "Loading assistant\u2026";
5611
5713
  return /* @__PURE__ */ jsxs17(Fragment4, { children: [
5612
- open && runtime?.config.presence ? /* @__PURE__ */ jsx25(
5714
+ open && runtime?.config.presence ? /* @__PURE__ */ jsx26(
5613
5715
  AgentConversationPresence,
5614
5716
  {
5615
5717
  agentId,
@@ -5621,15 +5723,15 @@ function EmbedRuntime({
5621
5723
  /* @__PURE__ */ jsxs17(
5622
5724
  PanelShell,
5623
5725
  {
5624
- actionsSlot: /* @__PURE__ */ jsx25(NewChatButton, { onPress: onNewConversation }),
5726
+ actionsSlot: /* @__PURE__ */ jsx26(NewChatButton, { onPress: onNewConversation }),
5727
+ agentId: options.agentId,
5625
5728
  defaultExpanded: options.panelExpanded,
5626
5729
  expandable: options.panelExpandable,
5730
+ expandedTitle: activeConversationTitle,
5627
5731
  modal: options.viewMode !== "sidebar",
5628
5732
  name: runtimeConfig?.name ?? "Data assistant",
5629
5733
  open,
5630
- agentId: options.agentId,
5631
- expandedTitle: activeConversationTitle,
5632
- expandedSidebarSlot: /* @__PURE__ */ jsx25(
5734
+ expandedSidebarSlot: /* @__PURE__ */ jsx26(
5633
5735
  SessionPicker,
5634
5736
  {
5635
5737
  activeConversationId,
@@ -5643,7 +5745,7 @@ function EmbedRuntime({
5643
5745
  onSelectConversation: selectConversation
5644
5746
  }
5645
5747
  ),
5646
- historySlot: /* @__PURE__ */ jsx25(
5748
+ historySlot: /* @__PURE__ */ jsx26(
5647
5749
  SessionPicker,
5648
5750
  {
5649
5751
  activeConversationId,
@@ -5658,13 +5760,13 @@ function EmbedRuntime({
5658
5760
  onClose,
5659
5761
  onNewConversation,
5660
5762
  children: [
5661
- visibleError ? /* @__PURE__ */ jsx25("div", { className: "ha-loading", role: "alert", children: visibleError }) : !runtime || !runtimeConfig ? /* @__PURE__ */ jsx25("div", { className: "ha-loading", children: /* @__PURE__ */ jsx25(ShimmerText, { className: "ha-progress ha-progress-shimmer", children: loadingMessage }) }) : null,
5662
- /* @__PURE__ */ jsx25(
5763
+ visibleError ? /* @__PURE__ */ jsx26("div", { className: "ha-loading", role: "alert", children: visibleError }) : !runtime || !runtimeConfig ? /* @__PURE__ */ jsx26("div", { className: "ha-loading", children: /* @__PURE__ */ jsx26(ShimmerText, { className: "ha-progress ha-progress-shimmer", children: loadingMessage }) }) : null,
5764
+ /* @__PURE__ */ jsx26(
5663
5765
  ConversationRouteStack,
5664
5766
  {
5665
5767
  activeConversationId: visibleError ? null : activeConversationId,
5666
5768
  routes: configuredRuntimeRoutes,
5667
- children: (route, active) => /* @__PURE__ */ jsx25(CardVariantProvider, { variant: route.config.surfaceVariant, children: /* @__PURE__ */ jsx25(
5769
+ children: (route, active) => /* @__PURE__ */ jsx26(CardVariantProvider, { variant: route.config.surfaceVariant, children: /* @__PURE__ */ jsx26(
5668
5770
  ChatRuntime,
5669
5771
  {
5670
5772
  active,
@@ -5725,11 +5827,11 @@ function ChatRuntime({
5725
5827
  const permissionStorageKey = agentPermissionModeStorageKey(storageKey, conversationId);
5726
5828
  const attachmentPolicyKey = options.composerAttachmentAccept;
5727
5829
  const attachmentPolicy = useMemo4(() => ({ key: attachmentPolicyKey }), [attachmentPolicyKey]);
5728
- const [attachmentSelection, setAttachmentSelection] = useState13(() => ({
5830
+ const [attachmentSelection, setAttachmentSelection] = useState14(() => ({
5729
5831
  files: [],
5730
5832
  policy: attachmentPolicy
5731
5833
  }));
5732
- const [attachmentErrorSelection, setAttachmentErrorSelection] = useState13(() => ({
5834
+ const [attachmentErrorSelection, setAttachmentErrorSelection] = useState14(() => ({
5733
5835
  error: null,
5734
5836
  policy: attachmentPolicy
5735
5837
  }));
@@ -5755,16 +5857,16 @@ function ChatRuntime({
5755
5857
  (error) => setAttachmentErrorSelection({ error, policy: attachmentPolicy }),
5756
5858
  [attachmentPolicy]
5757
5859
  );
5758
- const [activeTurnSelection, setActiveTurnSelectionState] = useState13(() => {
5860
+ const [activeTurnSelection, setActiveTurnSelectionState] = useState14(() => {
5759
5861
  const storedMode = shouldRestoreActivePermission ? readStoredAgentPermissionMode(activePermissionStorageKey) : void 0;
5760
5862
  const restoredMode = storedMode ?? (shouldRestoreActivePermission ? "ask" : void 0);
5761
5863
  return restoredMode ? { modelId: void 0, permissionMode: restoredMode } : void 0;
5762
5864
  });
5763
- const [activityTimings, setActivityTimings] = useState13({});
5764
- const [feedbackByMessage, setFeedbackByMessage] = useState13({});
5765
- const [isComposerExpanded, setIsComposerExpanded] = useState13(false);
5766
- const [input, setInput] = useState13("");
5767
- const [permissionSelection, setPermissionSelection] = useState13(() => {
5865
+ const [activityTimings, setActivityTimings] = useState14({});
5866
+ const [feedbackByMessage, setFeedbackByMessage] = useState14({});
5867
+ const [isComposerExpanded, setIsComposerExpanded] = useState14(false);
5868
+ const [input, setInput] = useState14("");
5869
+ const [permissionSelection, setPermissionSelection] = useState14(() => {
5768
5870
  const storedMode = options.permissionShowPicker ? readStoredAgentPermissionMode(permissionStorageKey) : void 0;
5769
5871
  const hasUnresolvedClientTool = options.permissionShowPicker && initialMessages.some(
5770
5872
  (message) => message.role === "assistant" && message.parts.some((part) => {
@@ -5780,20 +5882,20 @@ function ChatRuntime({
5780
5882
  selectedMode: storedMode ?? (hasUnresolvedClientTool ? "ask" : options.permissionDefaultMode)
5781
5883
  };
5782
5884
  });
5783
- const [isQueueReordering, setIsQueueReordering] = useState13(false);
5784
- const [isReconciling, setIsReconciling] = useState13(false);
5785
- const [localPending, setLocalPending] = useState13(false);
5786
- const [pendingClientToolCallIds, setPendingClientToolCallIds] = useState13(
5885
+ const [isQueueReordering, setIsQueueReordering] = useState14(false);
5886
+ const [isReconciling, setIsReconciling] = useState14(false);
5887
+ const [localPending, setLocalPending] = useState14(false);
5888
+ const [pendingClientToolCallIds, setPendingClientToolCallIds] = useState14(
5787
5889
  () => /* @__PURE__ */ new Set()
5788
5890
  );
5789
- const [isResuming, setIsResuming] = useState13(true);
5790
- const [progress, setProgress] = useState13(null);
5791
- const [queueDrainVersion, setQueueDrainVersion] = useState13(0);
5792
- const [reconcileEpoch, setReconcileEpoch] = useState13(0);
5793
- const [recoveryError, setRecoveryError] = useState13(false);
5794
- const [selection, setSelection] = useState13(null);
5795
- const [toolDecisions, setToolDecisions] = useState13({});
5796
- useEffect12(() => {
5891
+ const [isResuming, setIsResuming] = useState14(true);
5892
+ const [progress, setProgress] = useState14(null);
5893
+ const [queueDrainVersion, setQueueDrainVersion] = useState14(0);
5894
+ const [reconcileEpoch, setReconcileEpoch] = useState14(0);
5895
+ const [recoveryError, setRecoveryError] = useState14(false);
5896
+ const [selection, setSelection] = useState14(null);
5897
+ const [toolDecisions, setToolDecisions] = useState14({});
5898
+ useEffect14(() => {
5797
5899
  if (!shouldRestoreActivePermission) {
5798
5900
  clearStoredAgentPermissionMode(activePermissionStorageKey);
5799
5901
  }
@@ -5830,7 +5932,7 @@ function ChatRuntime({
5830
5932
  () => serializeClientTools(effectiveClientTools),
5831
5933
  [effectiveClientTools]
5832
5934
  );
5833
- const clientToolManifestRef = useRef14(clientToolManifest);
5935
+ const clientToolManifestRef = useRef15(clientToolManifest);
5834
5936
  useLayoutEffect4(() => {
5835
5937
  clientToolManifestRef.current = clientToolManifest;
5836
5938
  }, [clientToolManifest]);
@@ -5839,26 +5941,27 @@ function ChatRuntime({
5839
5941
  () => new Map(effectiveClientTools.map((tool) => [tool.name, tool])),
5840
5942
  [effectiveClientTools]
5841
5943
  );
5842
- const executedToolCallsRef = useRef14(/* @__PURE__ */ new Set());
5843
- const activeMessageIdRef = useRef14(null);
5844
- const isBusyRef = useRef14(true);
5845
- const isQueueReorderingRef = useRef14(false);
5846
- const messageQueueRef = useRef14(messageQueue);
5847
- const reconciliationDeferredRef = useRef14(null);
5848
- const reconcileMessageIdRef = useRef14(void 0);
5849
- const resumePromiseRef = useRef14(null);
5850
- const manualStopRequestedRef = useRef14(false);
5851
- const sendAcknowledgedRef = useRef14(false);
5852
- const sendErrorRef = useRef14(null);
5853
- const streamedMessageIdsRef = useRef14(/* @__PURE__ */ new Set());
5854
- const timedMessageIdRef = useRef14(null);
5855
- const transportFailureRef = useRef14(null);
5856
- const turnStartedAtRef = useRef14(null);
5857
- const needsReconciliationRef = useRef14(false);
5858
- const clearChatErrorRef = useRef14(() => void 0);
5859
- const fileInputRef = useRef14(null);
5860
- const messageQueueListRef = useRef14(null);
5861
- const textareaRef = useRef14(null);
5944
+ const executedToolCallsRef = useRef15(/* @__PURE__ */ new Set());
5945
+ const activeMessageIdRef = useRef15(null);
5946
+ const isBusyRef = useRef15(true);
5947
+ const isQueueReorderingRef = useRef15(false);
5948
+ const messageQueueRef = useRef15(messageQueue);
5949
+ const pendingTurnAnchorRef = useRef15(null);
5950
+ const reconciliationDeferredRef = useRef15(null);
5951
+ const reconcileMessageIdRef = useRef15(void 0);
5952
+ const resumePromiseRef = useRef15(null);
5953
+ const manualStopRequestedRef = useRef15(false);
5954
+ const sendAcknowledgedRef = useRef15(false);
5955
+ const sendErrorRef = useRef15(null);
5956
+ const streamedMessageIdsRef = useRef15(/* @__PURE__ */ new Set());
5957
+ const timedMessageIdRef = useRef15(null);
5958
+ const transportFailureRef = useRef15(null);
5959
+ const turnStartedAtRef = useRef15(null);
5960
+ const needsReconciliationRef = useRef15(false);
5961
+ const clearChatErrorRef = useRef15(() => void 0);
5962
+ const fileInputRef = useRef15(null);
5963
+ const messageQueueListRef = useRef15(null);
5964
+ const textareaRef = useRef15(null);
5862
5965
  const composerDraftKey = useMemo4(
5863
5966
  () => agentComposerDraftStorageKey(storageKey, conversationId),
5864
5967
  [conversationId, storageKey]
@@ -5897,13 +6000,13 @@ function ChatRuntime({
5897
6000
  transcribe: transcribeDictation,
5898
6001
  value: input
5899
6002
  });
5900
- useEffect12(() => {
6003
+ useEffect14(() => {
5901
6004
  messageQueueRef.current = messageQueue;
5902
6005
  }, [messageQueue]);
5903
- useEffect12(() => {
6006
+ useEffect14(() => {
5904
6007
  if (!options.composerDictation) cancelDictation();
5905
6008
  }, [cancelDictation, options.composerDictation]);
5906
- useEffect12(() => {
6009
+ useEffect14(() => {
5907
6010
  if (!active) cancelDictation();
5908
6011
  }, [active, cancelDictation]);
5909
6012
  const composerModelId = options.composerModelPicker ? selectedModelId : options.composerDefaultModel;
@@ -5970,7 +6073,6 @@ function ChatRuntime({
5970
6073
  }),
5971
6074
  onEvent: onTransportEvent,
5972
6075
  options,
5973
- realtimeUrl: options.triggerApiUrl ?? config.realtime?.url ?? DEFAULT_TRIGGER_API_URL,
5974
6076
  tokenManager
5975
6077
  });
5976
6078
  const chatState = useChat({
@@ -6040,7 +6142,7 @@ function ChatRuntime({
6040
6142
  useLayoutEffect4(() => {
6041
6143
  clearChatErrorRef.current = clearError;
6042
6144
  }, [clearError]);
6043
- useEffect12(() => {
6145
+ useEffect14(() => {
6044
6146
  const resumePromise = resumePromiseRef.current ?? chatState.resumeStream();
6045
6147
  resumePromiseRef.current = resumePromise;
6046
6148
  void resumePromise.then(
@@ -6081,7 +6183,7 @@ function ChatRuntime({
6081
6183
  recoveryError
6082
6184
  });
6083
6185
  const reportConversationStatusChange = useEffectEvent(onConversationStatusChange);
6084
- useEffect12(() => {
6186
+ useEffect14(() => {
6085
6187
  if (!localPending || !isRunning || isReconciling) return;
6086
6188
  const timeoutId = window.setTimeout(() => {
6087
6189
  if (shouldReconcileStalledAgentTurn({
@@ -6096,7 +6198,7 @@ function ChatRuntime({
6096
6198
  }, TRANSPORT_COMPLETION_WATCHDOG_MS);
6097
6199
  return () => window.clearTimeout(timeoutId);
6098
6200
  }, [isReconciling, isRunning, localPending, scheduleReconciliation]);
6099
- useEffect12(() => {
6201
+ useEffect14(() => {
6100
6202
  if (!activeTurnSelection) return;
6101
6203
  if (!isRunning && !hasPendingClientToolCalls && !localPending && !isReconciling && !isResuming && !isAutoContinuationPending) {
6102
6204
  clearStoredAgentPermissionMode(activePermissionStorageKey);
@@ -6112,17 +6214,17 @@ function ChatRuntime({
6112
6214
  isRunning,
6113
6215
  localPending
6114
6216
  ]);
6115
- useEffect12(() => {
6217
+ useEffect14(() => {
6116
6218
  isBusyRef.current = isBusy;
6117
6219
  }, [isBusy]);
6118
- useEffect12(() => {
6220
+ useEffect14(() => {
6119
6221
  reportConversationStatusChange(conversationId, {
6120
6222
  busy: isConversationBusy,
6121
6223
  failed: conversationFailed,
6122
6224
  turnActive: hasActiveTurn
6123
6225
  });
6124
6226
  }, [conversationFailed, conversationId, hasActiveTurn, isConversationBusy]);
6125
- useEffect12(
6227
+ useEffect14(
6126
6228
  () => () => {
6127
6229
  reportConversationStatusChange(conversationId, {
6128
6230
  busy: false,
@@ -6138,7 +6240,7 @@ function ChatRuntime({
6138
6240
  const hasStreamingActivity = Boolean(
6139
6241
  streamingAssistantMessage && collectActivity(streamingAssistantMessage, clientToolsByName, { isStreaming: true }).length > 0
6140
6242
  );
6141
- useEffect12(() => {
6243
+ useEffect14(() => {
6142
6244
  if (hasActiveTurn && streamingAssistantMessageId) {
6143
6245
  if (timedMessageIdRef.current === streamingAssistantMessageId) return;
6144
6246
  const startedAt = turnStartedAtRef.current ?? Date.now();
@@ -6197,7 +6299,7 @@ function ChatRuntime({
6197
6299
  },
6198
6300
  [conversationId, options, setClientToolCallPending, submitToolOutput, tokenManager]
6199
6301
  );
6200
- useEffect12(() => {
6302
+ useEffect14(() => {
6201
6303
  for (const message of chatState.messages) {
6202
6304
  if (message.role !== "assistant") continue;
6203
6305
  for (const part of message.parts) {
@@ -6231,7 +6333,7 @@ function ChatRuntime({
6231
6333
  submitToolOutput,
6232
6334
  toolDecisions
6233
6335
  ]);
6234
- useEffect12(() => {
6336
+ useEffect14(() => {
6235
6337
  if (reconcileEpoch === 0) return;
6236
6338
  let cancelled = false;
6237
6339
  void (async () => {
@@ -6328,7 +6430,7 @@ function ChatRuntime({
6328
6430
  }
6329
6431
  startDictation();
6330
6432
  }, [cancelDictation, isDictating, isDictationTranscribing, startDictation, stopDictation]);
6331
- useEffect12(() => {
6433
+ useEffect14(() => {
6332
6434
  if (!active || !options.composerDictation || !isDictationSupported) return;
6333
6435
  const handleDictationShortcut = (event) => {
6334
6436
  if (!isDictationShortcut(event)) return;
@@ -6341,6 +6443,9 @@ function ChatRuntime({
6341
6443
  const sendAgentMessage = useCallback11(
6342
6444
  async (message) => {
6343
6445
  const previousMessages = chatState.messages;
6446
+ const previousUserMessageId = previousMessages.findLast(
6447
+ (previousMessage) => previousMessage.role === "user"
6448
+ )?.id;
6344
6449
  const messagePermissionMode = options.permissionShowPicker ? message.permissionMode ?? composerPermissionMode : options.permissionDefaultMode;
6345
6450
  const messageClientToolManifest = serializeClientTools(
6346
6451
  applyAgentPermissionMode(options.tools, messagePermissionMode)
@@ -6353,9 +6458,13 @@ function ChatRuntime({
6353
6458
  timedMessageIdRef.current = null;
6354
6459
  turnStartedAtRef.current = Date.now();
6355
6460
  prepareTransportSend();
6461
+ pendingTurnAnchorRef.current = {
6462
+ messageId: message.id,
6463
+ previousUserMessageId: previousUserMessageId ?? null
6464
+ };
6356
6465
  setLocalPending(true);
6357
6466
  setRecoveryError(false);
6358
- setProgress("Understanding your request\u2026");
6467
+ setProgress(DEFAULT_ACTIVITY_STATUS);
6359
6468
  onConversationStarted(titleFromPrompt(message.text, message.files));
6360
6469
  try {
6361
6470
  const pageContext = await getSafePageContext(options, message.selection);
@@ -6375,7 +6484,7 @@ function ChatRuntime({
6375
6484
  pageContext,
6376
6485
  webSearch: options.webSearch
6377
6486
  };
6378
- setProgress("Understanding your request\u2026");
6487
+ setProgress(DEFAULT_ACTIVITY_STATUS);
6379
6488
  await chatState.sendMessage(createAgentMessageSendInput(message, fileParts), {
6380
6489
  metadata
6381
6490
  });
@@ -6414,10 +6523,11 @@ function ChatRuntime({
6414
6523
  const sendMessageNow = useCallback11(
6415
6524
  (message, { resumeDrainAfter = true } = {}) => {
6416
6525
  activeMessageIdRef.current = message.id;
6417
- void sendAgentMessage(message).catch(() => {
6526
+ void sendAgentMessage(message).catch((sendError) => {
6418
6527
  if (activeMessageIdRef.current === message.id) {
6419
6528
  activeMessageIdRef.current = null;
6420
6529
  }
6530
+ if (!shouldRequeueAgentMessage(sendError)) return;
6421
6531
  setMessageQueue(
6422
6532
  (current) => setBlockedAgentMessageId(enqueueAgentMessage(current, message, "prepend"), message.id)
6423
6533
  );
@@ -6475,14 +6585,14 @@ function ChatRuntime({
6475
6585
  );
6476
6586
  const visibleSuggestedPrompts = config.suggestedPrompts ?? [];
6477
6587
  const replayShellHandoff = useEffectEvent((prompt) => submitComposerMessage(prompt));
6478
- useEffect12(() => {
6588
+ useEffect14(() => {
6479
6589
  if (!active) return;
6480
6590
  const handoff = takeAgentShellHandoff(options.agentId);
6481
6591
  if (!handoff) return;
6482
6592
  if (handoff.submitted) replayShellHandoff(handoff.prompt);
6483
6593
  else setInput((current) => current || handoff.prompt);
6484
6594
  }, [active, options.agentId]);
6485
- useEffect12(() => {
6595
+ useEffect14(() => {
6486
6596
  if (!active || !options.suggestedPromptShortcuts || chatState.messages.length > 0 || visibleSuggestedPrompts.length === 0) {
6487
6597
  return;
6488
6598
  }
@@ -6515,7 +6625,7 @@ function ChatRuntime({
6515
6625
  isQueueReorderingRef.current = isReordering;
6516
6626
  setIsQueueReordering(isReordering);
6517
6627
  }, []);
6518
- useEffect12(() => {
6628
+ useEffect14(() => {
6519
6629
  const nextMessage = getNextDrainableAgentMessage({
6520
6630
  activeMessageId: activeMessageIdRef.current,
6521
6631
  isBusy,
@@ -6631,21 +6741,25 @@ function ChatRuntime({
6631
6741
  return;
6632
6742
  }
6633
6743
  }
6634
- if (action.type === "submit") {
6635
- setSelection({
6636
- componentId: action.componentId,
6637
- componentTitle: action.componentTitle,
6638
- datum: { actionId: action.actionId, values: action.values }
6639
- });
6640
- }
6641
- const prompt = action.type === "followup" || action.type === "submit" || action.type === "tool" ? action.prompt ?? (action.type === "tool" ? action.label : void 0) : void 0;
6642
- if (!prompt) return;
6643
- cancelDictation();
6644
- setInput(prompt);
6645
- setIsComposerExpanded(true);
6646
- requestAnimationFrame(() => textareaRef.current?.focus());
6744
+ routeComponentPromptAction(action, {
6745
+ fillComposer: (prompt) => {
6746
+ cancelDictation();
6747
+ setInput(prompt);
6748
+ setIsComposerExpanded(true);
6749
+ requestAnimationFrame(() => textareaRef.current?.focus());
6750
+ },
6751
+ setSelection,
6752
+ submitPrompt: submitComposerMessage
6753
+ });
6647
6754
  },
6648
- [cancelDictation, clientToolsByName, conversationId, options, tokenManager]
6755
+ [
6756
+ cancelDictation,
6757
+ clientToolsByName,
6758
+ conversationId,
6759
+ options,
6760
+ submitComposerMessage,
6761
+ tokenManager
6762
+ ]
6649
6763
  );
6650
6764
  const copyMessage = useCallback11(
6651
6765
  (message) => {
@@ -6712,6 +6826,7 @@ function ChatRuntime({
6712
6826
  permissionMode: composerPermissionMode
6713
6827
  });
6714
6828
  prepareTransportSend();
6829
+ pendingTurnAnchorRef.current = null;
6715
6830
  setLocalPending(true);
6716
6831
  setRecoveryError(false);
6717
6832
  void reportEvent(options, tokenManager, conversationId, "retry");
@@ -6744,7 +6859,12 @@ function ChatRuntime({
6744
6859
  (message) => message.role === "assistant"
6745
6860
  )?.id;
6746
6861
  const lastUserMessageId = chatState.messages.findLast((message) => message.role === "user")?.id;
6747
- const activeStreamKey = localPending || isRunning ? lastUserMessageId ?? conversationId : null;
6862
+ const { activeStreamKey, activeTurnMessageId } = resolveConversationTurnAnchor({
6863
+ conversationId,
6864
+ isActive: localPending || isRunning,
6865
+ lastUserMessageId,
6866
+ pendingTurn: pendingTurnAnchorRef.current
6867
+ });
6748
6868
  const { showHome, standaloneStatus } = getConversationPresentation({
6749
6869
  hasError: Boolean(chatState.error || recoveryError),
6750
6870
  hasStreamingActivity,
@@ -6760,13 +6880,13 @@ function ChatRuntime({
6760
6880
  return /* @__PURE__ */ jsxs17(Fragment4, { children: [
6761
6881
  /* @__PURE__ */ jsxs17(ConversationViewport, { streamKey: activeStreamKey, children: [
6762
6882
  showHome ? /* @__PURE__ */ jsxs17("div", { className: "ha-empty", children: [
6763
- /* @__PURE__ */ jsx25("h2", { children: options.greeting }),
6764
- /* @__PURE__ */ jsx25("p", { children: "Live answers with charts, metrics, and tables." })
6765
- ] }) : chatState.messages.map((message) => /* @__PURE__ */ jsx25(
6883
+ /* @__PURE__ */ jsx26("h2", { children: options.greeting }),
6884
+ /* @__PURE__ */ jsx26("p", { children: "Live answers with charts, metrics, and tables." })
6885
+ ] }) : chatState.messages.map((message) => /* @__PURE__ */ jsx26(
6766
6886
  Message,
6767
6887
  {
6768
- activityTiming: activityTimings[message.id],
6769
6888
  actions: options.messageActions,
6889
+ activityTiming: activityTimings[message.id],
6770
6890
  canRetry: message.id === lastAssistantMessageId && !isBusy,
6771
6891
  clientTools: clientToolsByName,
6772
6892
  exportFormats: options.componentExportFormats,
@@ -6778,6 +6898,7 @@ function ChatRuntime({
6778
6898
  message,
6779
6899
  progress: message.id === streamingAssistantMessageId ? progress : null,
6780
6900
  toolDecisions,
6901
+ turnAnchor: message.id === activeTurnMessageId && activeStreamKey !== null ? "active" : message.id === lastUserMessageId ? "latest" : null,
6781
6902
  onAction: handleComponentAction,
6782
6903
  onClientToolDecision: decideClientTool,
6783
6904
  onCopy: copyMessage,
@@ -6787,14 +6908,14 @@ function ChatRuntime({
6787
6908
  },
6788
6909
  message.id
6789
6910
  )),
6790
- standaloneStatus ? /* @__PURE__ */ jsx25("div", { className: "ha-message", "data-role": "assistant", "data-stream-anchor": "true", children: /* @__PURE__ */ jsx25("div", { className: "ha-message-body", children: /* @__PURE__ */ jsx25(ActivityStatus, { label: standaloneStatus }) }) }) : null,
6911
+ standaloneStatus && (active || standaloneStatus !== DEFAULT_ACTIVITY_STATUS) ? /* @__PURE__ */ jsx26("div", { className: "ha-message", "data-role": "assistant", "data-stream-anchor": "true", children: /* @__PURE__ */ jsx26("div", { className: "ha-message-body", children: standaloneStatus === DEFAULT_ACTIVITY_STATUS ? /* @__PURE__ */ jsx26(StartupActivityStatus, {}, activeStreamKey ?? conversationId) : /* @__PURE__ */ jsx26(ActivityStatus, { label: standaloneStatus }) }) }) : null,
6791
6912
  chatState.error || recoveryError ? /* @__PURE__ */ jsxs17("div", { className: "ha-error", role: "alert", children: [
6792
- /* @__PURE__ */ jsx25("span", { children: "The request could not be completed." }),
6793
- /* @__PURE__ */ jsx25("button", { type: "button", onClick: retryLastResponse, children: "Retry" })
6913
+ /* @__PURE__ */ jsx26("span", { children: "The request could not be completed." }),
6914
+ /* @__PURE__ */ jsx26("button", { type: "button", onClick: retryLastResponse, children: "Retry" })
6794
6915
  ] }) : null
6795
6916
  ] }),
6796
6917
  /* @__PURE__ */ jsxs17("form", { className: "ha-composer-wrap", onSubmit: submit, children: [
6797
- showHome && visibleSuggestedPrompts.length > 0 ? /* @__PURE__ */ jsx25("div", { "aria-label": "Suggested prompts", className: "ha-suggestions", role: "group", children: visibleSuggestedPrompts.map((prompt, index) => /* @__PURE__ */ jsxs17(
6918
+ showHome && visibleSuggestedPrompts.length > 0 ? /* @__PURE__ */ jsx26("div", { "aria-label": "Suggested prompts", className: "ha-suggestions", role: "group", children: visibleSuggestedPrompts.map((prompt, index) => /* @__PURE__ */ jsxs17(
6798
6919
  "button",
6799
6920
  {
6800
6921
  className: "ha-suggestion",
@@ -6802,17 +6923,17 @@ function ChatRuntime({
6802
6923
  "aria-keyshortcuts": options.suggestedPromptShortcuts ? `Control+${index + 1}` : void 0,
6803
6924
  onClick: () => submitComposerMessage(prompt),
6804
6925
  children: [
6805
- /* @__PURE__ */ jsx25("span", { children: prompt }),
6926
+ /* @__PURE__ */ jsx26("span", { children: prompt }),
6806
6927
  options.suggestedPromptShortcuts ? /* @__PURE__ */ jsxs17("span", { "aria-hidden": "true", className: "ha-suggestion-shortcut", children: [
6807
- /* @__PURE__ */ jsx25("kbd", { children: "Ctrl" }),
6808
- /* @__PURE__ */ jsx25("kbd", { children: index + 1 })
6809
- ] }) : /* @__PURE__ */ jsx25(ArrowUpRight, { "aria-hidden": "true" })
6928
+ /* @__PURE__ */ jsx26("kbd", { children: "Ctrl" }),
6929
+ /* @__PURE__ */ jsx26("kbd", { children: index + 1 })
6930
+ ] }) : /* @__PURE__ */ jsx26(ArrowUpRight, { "aria-hidden": "true" })
6810
6931
  ]
6811
6932
  },
6812
6933
  prompt
6813
6934
  )) }) : null,
6814
- selection ? /* @__PURE__ */ jsx25(ComposerContext, { title: selection.componentTitle, onRemove: () => setSelection(null) }) : null,
6815
- /* @__PURE__ */ jsx25(
6935
+ selection ? /* @__PURE__ */ jsx26(ComposerContext, { title: selection.componentTitle, onRemove: () => setSelection(null) }) : null,
6936
+ /* @__PURE__ */ jsx26(
6816
6937
  MessageQueueList,
6817
6938
  {
6818
6939
  ref: messageQueueListRef,
@@ -6836,7 +6957,7 @@ function ChatRuntime({
6836
6957
  disabled: options.composerAttachmentContentTypes.length === 0,
6837
6958
  onFilesSelected: addComposerAttachments,
6838
6959
  children: [
6839
- options.composerAttachmentContentTypes.length > 0 ? /* @__PURE__ */ jsx25(
6960
+ options.composerAttachmentContentTypes.length > 0 ? /* @__PURE__ */ jsx26(
6840
6961
  "input",
6841
6962
  {
6842
6963
  ref: fileInputRef,
@@ -6852,14 +6973,14 @@ function ChatRuntime({
6852
6973
  }
6853
6974
  }
6854
6975
  ) : null,
6855
- attachments.length > 0 ? /* @__PURE__ */ jsx25(
6976
+ attachments.length > 0 ? /* @__PURE__ */ jsx26(
6856
6977
  ScrollShadow,
6857
6978
  {
6858
6979
  "aria-label": "Selected attachments",
6859
6980
  className: "ha-attachments",
6860
6981
  offset: 1,
6861
6982
  size: 24,
6862
- children: attachments.map((file) => /* @__PURE__ */ jsx25(
6983
+ children: attachments.map((file) => /* @__PURE__ */ jsx26(
6863
6984
  ComposerAttachment,
6864
6985
  {
6865
6986
  file,
@@ -6872,17 +6993,17 @@ function ChatRuntime({
6872
6993
  ))
6873
6994
  }
6874
6995
  ) : null,
6875
- options.composerAttachmentContentTypes.length > 0 ? /* @__PURE__ */ jsx25(
6996
+ options.composerAttachmentContentTypes.length > 0 ? /* @__PURE__ */ jsx26(
6876
6997
  "button",
6877
6998
  {
6878
6999
  "aria-label": "Attach files",
6879
7000
  className: "ha-attach",
6880
7001
  type: "button",
6881
7002
  onClick: () => fileInputRef.current?.click(),
6882
- children: /* @__PURE__ */ jsx25(Plus3, { "aria-hidden": "true" })
7003
+ children: /* @__PURE__ */ jsx26(Plus3, { "aria-hidden": "true" })
6883
7004
  }
6884
7005
  ) : null,
6885
- /* @__PURE__ */ jsx25(
7006
+ /* @__PURE__ */ jsx26(
6886
7007
  "textarea",
6887
7008
  {
6888
7009
  ref: textareaRef,
@@ -6909,8 +7030,8 @@ function ChatRuntime({
6909
7030
  }
6910
7031
  }
6911
7032
  ),
6912
- isDictating ? /* @__PURE__ */ jsx25("div", { className: "ha-dictation-session", children: isDictationTranscribing ? /* @__PURE__ */ jsx25("span", { className: "ha-dictation-status", role: "status", children: "Transcribing\u2026" }) : /* @__PURE__ */ jsxs17(Fragment4, { children: [
6913
- /* @__PURE__ */ jsx25(
7033
+ isDictating ? /* @__PURE__ */ jsx26("div", { className: "ha-dictation-session", children: isDictationTranscribing ? /* @__PURE__ */ jsx26("span", { className: "ha-dictation-status", role: "status", children: "Transcribing\u2026" }) : /* @__PURE__ */ jsxs17(Fragment4, { children: [
7034
+ /* @__PURE__ */ jsx26(
6914
7035
  VoiceLiveWaveform,
6915
7036
  {
6916
7037
  active: isDictationRecording,
@@ -6920,7 +7041,7 @@ function ChatRuntime({
6920
7041
  stream: dictationStream
6921
7042
  }
6922
7043
  ),
6923
- /* @__PURE__ */ jsx25(
7044
+ /* @__PURE__ */ jsx26(
6924
7045
  "time",
6925
7046
  {
6926
7047
  "aria-label": `Recording duration ${formatVoiceDuration(dictationElapsedSeconds)}`,
@@ -6930,7 +7051,7 @@ function ChatRuntime({
6930
7051
  }
6931
7052
  )
6932
7053
  ] }) }) : null,
6933
- options.permissionShowPicker && options.tools.length > 0 && !isDictating ? /* @__PURE__ */ jsx25(
7054
+ options.permissionShowPicker && options.tools.length > 0 && !isDictating ? /* @__PURE__ */ jsx26(
6934
7055
  PermissionPicker,
6935
7056
  {
6936
7057
  "aria-label": "Tool permissions",
@@ -6940,7 +7061,7 @@ function ChatRuntime({
6940
7061
  onChange: setSelectedPermissionMode
6941
7062
  }
6942
7063
  ) : null,
6943
- options.composerModelPicker && !isDictating ? /* @__PURE__ */ jsx25(
7064
+ options.composerModelPicker && !isDictating ? /* @__PURE__ */ jsx26(
6944
7065
  ModelPicker,
6945
7066
  {
6946
7067
  "aria-label": "AI model",
@@ -6964,8 +7085,8 @@ function ChatRuntime({
6964
7085
  type: "button",
6965
7086
  onClick: toggleDictation,
6966
7087
  children: [
6967
- isDictationTranscribing ? /* @__PURE__ */ jsx25(Xmark3, { "aria-hidden": "true" }) : isDictating ? /* @__PURE__ */ jsx25(StopFill, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx25(Microphone, { "aria-hidden": "true" }),
6968
- /* @__PURE__ */ jsx25(
7088
+ isDictationTranscribing ? /* @__PURE__ */ jsx26(Xmark3, { "aria-hidden": "true" }) : isDictating ? /* @__PURE__ */ jsx26(StopFill, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx26(Microphone, { "aria-hidden": "true" }),
7089
+ /* @__PURE__ */ jsx26(
6969
7090
  ComposerControlTooltip,
6970
7091
  {
6971
7092
  shortcut: isDictationSupported ? DICTATION_SHORTCUT_LABEL : void 0,
@@ -6975,7 +7096,7 @@ function ChatRuntime({
6975
7096
  ]
6976
7097
  }
6977
7098
  ) : null,
6978
- attachmentError || options.composerDictation && dictationError ? /* @__PURE__ */ jsx25("span", { className: "ha-dictation-error", role: "alert", children: attachmentError ?? dictationError }) : null,
7099
+ attachmentError || options.composerDictation && dictationError ? /* @__PURE__ */ jsx26("span", { className: "ha-dictation-error", role: "alert", children: attachmentError ?? dictationError }) : null,
6979
7100
  isRunning && !hasComposerDraft ? /* @__PURE__ */ jsxs17(
6980
7101
  "button",
6981
7102
  {
@@ -6987,8 +7108,8 @@ function ChatRuntime({
6987
7108
  type: "button",
6988
7109
  onClick: stopGeneration,
6989
7110
  children: [
6990
- /* @__PURE__ */ jsx25(StopFill, { "aria-hidden": "true" }),
6991
- /* @__PURE__ */ jsx25(ComposerControlTooltip, { shortcut: COMPOSER_ACTION_SHORTCUT_LABEL, children: sendButtonLabel })
7111
+ /* @__PURE__ */ jsx26(StopFill, { "aria-hidden": "true" }),
7112
+ /* @__PURE__ */ jsx26(ComposerControlTooltip, { shortcut: COMPOSER_ACTION_SHORTCUT_LABEL, children: sendButtonLabel })
6992
7113
  ]
6993
7114
  }
6994
7115
  ) : /* @__PURE__ */ jsxs17(
@@ -7002,16 +7123,16 @@ function ChatRuntime({
7002
7123
  disabled: !hasComposerDraft || isDictating,
7003
7124
  type: "submit",
7004
7125
  children: [
7005
- /* @__PURE__ */ jsx25(ArrowUp2, { "aria-hidden": "true" }),
7006
- /* @__PURE__ */ jsx25(ComposerControlTooltip, { shortcut: COMPOSER_ACTION_SHORTCUT_LABEL, children: sendButtonLabel })
7126
+ /* @__PURE__ */ jsx26(ArrowUp2, { "aria-hidden": "true" }),
7127
+ /* @__PURE__ */ jsx26(ComposerControlTooltip, { shortcut: COMPOSER_ACTION_SHORTCUT_LABEL, children: sendButtonLabel })
7007
7128
  ]
7008
7129
  }
7009
7130
  )
7010
7131
  ]
7011
7132
  }
7012
7133
  ),
7013
- options.composerDisclaimer ? /* @__PURE__ */ jsx25("p", { className: "ha-footnote", children: renderInlineMarkdown(options.composerDisclaimer) }) : null,
7014
- /* @__PURE__ */ jsx25(PoweredByHero, {})
7134
+ options.composerDisclaimer ? /* @__PURE__ */ jsx26("p", { className: "ha-footnote", children: renderInlineMarkdown(options.composerDisclaimer) }) : null,
7135
+ /* @__PURE__ */ jsx26(PoweredByHero, {})
7015
7136
  ] })
7016
7137
  ] });
7017
7138
  }
@@ -7034,23 +7155,25 @@ function Message({
7034
7155
  onRetry,
7035
7156
  onSelect,
7036
7157
  progress,
7037
- toolDecisions
7158
+ toolDecisions,
7159
+ turnAnchor
7038
7160
  }) {
7039
7161
  const activity = collectActivity(message, clientTools, { isStreaming });
7040
7162
  const sources = collectMessageSources(message.parts);
7163
+ const isBuildingComponent = message.parts.some(isStreamingComponentBuild);
7164
+ useEffect14(() => {
7165
+ if (isBuildingComponent) preloadGenUIRenderer();
7166
+ }, [isBuildingComponent]);
7041
7167
  const activeTextPartIndex = isStreaming ? message.parts.findLastIndex((part) => part.type === "text") : -1;
7042
7168
  const userAttachments = message.role === "user" ? message.parts.flatMap(
7043
- (part, index) => part.type === "file" ? [/* @__PURE__ */ jsx25(MessageAttachment, { file: part }, `${message.id}-attachment-${index}`)] : []
7169
+ (part, index) => part.type === "file" ? [/* @__PURE__ */ jsx26(MessageAttachment, { file: part }, `${message.id}-attachment-${index}`)] : []
7044
7170
  ) : [];
7045
- const lastComponentPartIndex = message.parts.findLastIndex(
7046
- (part) => part.type === "data-component"
7047
- );
7048
7171
  const content = message.parts.reduce((items, part, index) => {
7049
7172
  if (part.type === "text") {
7050
7173
  if (!part.text.trim()) return items;
7051
7174
  return [
7052
7175
  ...items,
7053
- message.role === "assistant" ? /* @__PURE__ */ jsx25(
7176
+ message.role === "assistant" ? /* @__PURE__ */ jsx26(
7054
7177
  ConversationMarkdown,
7055
7178
  {
7056
7179
  animated: markdownAnimation,
@@ -7060,17 +7183,17 @@ function Message({
7060
7183
  children: part.text
7061
7184
  },
7062
7185
  `${message.id}-text-${index}`
7063
- ) : /* @__PURE__ */ jsx25("p", { className: "ha-message-text", children: part.text }, `${message.id}-text-${index}`)
7186
+ ) : /* @__PURE__ */ jsx26("p", { className: "ha-message-text", children: part.text }, `${message.id}-text-${index}`)
7064
7187
  ];
7065
7188
  }
7066
7189
  if (part.type === "file") {
7067
7190
  if (message.role === "user") return items;
7068
- return [...items, /* @__PURE__ */ jsx25(MessageAttachment, { file: part }, `${message.id}-file-${index}`)];
7191
+ return [...items, /* @__PURE__ */ jsx26(MessageAttachment, { file: part }, `${message.id}-file-${index}`)];
7069
7192
  }
7070
7193
  if (part.type === "data-component") {
7071
7194
  return [
7072
7195
  ...items,
7073
- /* @__PURE__ */ jsx25(
7196
+ /* @__PURE__ */ jsx26(
7074
7197
  GenUIRenderer,
7075
7198
  {
7076
7199
  component: part.data,
@@ -7082,20 +7205,7 @@ function Message({
7082
7205
  )
7083
7206
  ];
7084
7207
  }
7085
- const componentPreview2 = streamingComponentPreview(part);
7086
- if (componentPreview2 && index > lastComponentPartIndex) {
7087
- return [
7088
- ...items,
7089
- /* @__PURE__ */ jsx25(
7090
- ComponentSkeleton,
7091
- {
7092
- kind: componentPreview2.kind,
7093
- title: componentPreview2.title
7094
- },
7095
- componentPreview2.toolCallId
7096
- )
7097
- ];
7098
- }
7208
+ if (isStreamingComponentBuild(part)) return items;
7099
7209
  const clientToolName = clientToolPartName(part);
7100
7210
  const clientTool = clientToolName ? clientTools.get(clientToolName) : void 0;
7101
7211
  if (clientToolName && clientTool?.needsApproval) {
@@ -7103,7 +7213,7 @@ function Message({
7103
7213
  if (toolPart.state !== "input-available" || toolDecisions[toolPart.toolCallId]) return items;
7104
7214
  return [
7105
7215
  ...items,
7106
- /* @__PURE__ */ jsx25(
7216
+ /* @__PURE__ */ jsx26(
7107
7217
  ClientToolCard,
7108
7218
  {
7109
7219
  decision: toolDecisions[toolPart.toolCallId],
@@ -7125,22 +7235,31 @@ function Message({
7125
7235
  return null;
7126
7236
  }
7127
7237
  if (message.role === "user") {
7128
- return /* @__PURE__ */ jsxs17("div", { className: "ha-message", "data-role": "user", children: [
7129
- userAttachments.length > 0 ? /* @__PURE__ */ jsx25("div", { "aria-label": "Message attachments", className: "ha-message-attachments", children: userAttachments }) : null,
7130
- content.length > 0 ? /* @__PURE__ */ jsx25("div", { className: "ha-bubble", children: /* @__PURE__ */ jsx25("div", { className: "ha-message-content", children: content }) }) : null
7131
- ] });
7238
+ return /* @__PURE__ */ jsxs17(
7239
+ "div",
7240
+ {
7241
+ className: "ha-message",
7242
+ "data-role": "user",
7243
+ "data-stream-anchor": turnAnchor === "active" ? true : void 0,
7244
+ "data-turn-anchor": turnAnchor ? true : void 0,
7245
+ children: [
7246
+ userAttachments.length > 0 ? /* @__PURE__ */ jsx26("div", { "aria-label": "Message attachments", className: "ha-message-attachments", children: userAttachments }) : null,
7247
+ content.length > 0 ? /* @__PURE__ */ jsx26("div", { className: "ha-bubble", children: /* @__PURE__ */ jsx26("div", { className: "ha-message-content", children: content }) }) : null
7248
+ ]
7249
+ }
7250
+ );
7132
7251
  }
7133
7252
  const hasCopyableText = message.parts.some(
7134
7253
  (part) => part.type === "text" && part.text.trim().length > 0
7135
7254
  );
7136
- return /* @__PURE__ */ jsx25(
7255
+ return /* @__PURE__ */ jsx26(
7137
7256
  "div",
7138
7257
  {
7139
7258
  className: "ha-message",
7140
7259
  "data-role": "assistant",
7141
7260
  "data-stream-anchor": isStreaming ? true : void 0,
7142
7261
  children: /* @__PURE__ */ jsxs17("div", { className: "ha-message-body", children: [
7143
- activity.length > 0 ? /* @__PURE__ */ jsx25(
7262
+ activity.length > 0 ? /* @__PURE__ */ jsx26(
7144
7263
  ActivityTrail,
7145
7264
  {
7146
7265
  completedAt: activityTiming?.completedAt,
@@ -7150,9 +7269,9 @@ function Message({
7150
7269
  startedAt: activityTiming?.startedAt
7151
7270
  }
7152
7271
  ) : null,
7153
- content.length > 0 ? /* @__PURE__ */ jsx25("div", { className: "ha-message-content", children: content }) : null,
7154
- /* @__PURE__ */ jsx25(MessageSources, { sources: visibleSources }),
7155
- !isStreaming && content.length > 0 && actions.length > 0 ? /* @__PURE__ */ jsx25(
7272
+ content.length > 0 ? /* @__PURE__ */ jsx26("div", { className: "ha-message-content", children: content }) : null,
7273
+ /* @__PURE__ */ jsx26(MessageSources, { sources: visibleSources }),
7274
+ !isStreaming && content.length > 0 && actions.length > 0 ? /* @__PURE__ */ jsx26(
7156
7275
  MessageActions,
7157
7276
  {
7158
7277
  actions,
@@ -7174,11 +7293,11 @@ function ClientToolCard({
7174
7293
  }) {
7175
7294
  if (part.state !== "input-available" || decision) return null;
7176
7295
  const Icon = tool.icon;
7177
- return /* @__PURE__ */ jsx25(
7296
+ return /* @__PURE__ */ jsx26(
7178
7297
  ApprovalCard,
7179
7298
  {
7180
7299
  "data-tool-status": "approval-requested",
7181
- icon: Icon ? /* @__PURE__ */ jsx25(Icon, { color: tool.iconColor, size: 16 }) : void 0,
7300
+ icon: Icon ? /* @__PURE__ */ jsx26(Icon, { color: tool.iconColor, size: 16 }) : void 0,
7182
7301
  input: part.input,
7183
7302
  title: tool.displayName ?? tool.name,
7184
7303
  onApprove: () => onDecision(true),
@@ -7193,7 +7312,6 @@ function useAgentTransport({
7193
7312
  modelId,
7194
7313
  onEvent,
7195
7314
  options,
7196
- realtimeUrl,
7197
7315
  tokenManager
7198
7316
  }) {
7199
7317
  const sessionKey = `heroui-agent:session:${options.agentId}:${conversationId}`;
@@ -7208,41 +7326,19 @@ function useAgentTransport({
7208
7326
  const session = restoreTriggerSession(stored, bootstrapStatus);
7209
7327
  return session ? { [conversationId]: session } : void 0;
7210
7328
  }, [bootstrapStatus, conversationId, sessionKey]);
7211
- const reportedReconnectRef = useRef14(false);
7329
+ const reportedReconnectRef = useRef15(false);
7212
7330
  const authorizedFetch = useCallback11(
7213
- async (url, init = {}) => {
7214
- const request = async () => {
7215
- const headers = new Headers(init.headers);
7216
- headers.set("Authorization", `Bearer ${await tokenManager.get()}`);
7217
- headers.set("X-HeroUI-Agent-Protocol-Version", String(HEROUI_AGENT_PROTOCOL_VERSION));
7218
- headers.set("X-HeroUI-Agent-SDK-Version", HEROUI_AGENT_SDK_VERSION);
7219
- return fetch(url, { ...init, headers });
7220
- };
7221
- let response = await request();
7222
- if (response.status === 401) {
7223
- tokenManager.clear();
7224
- response = await request();
7225
- }
7226
- return response;
7227
- },
7331
+ (url, init = {}) => fetchWithAgentAuthorization(tokenManager, url, init),
7228
7332
  [tokenManager]
7229
7333
  );
7230
7334
  return useTriggerChatTransport({
7231
- // Answers stream directly from Trigger.dev with a short-lived read-only
7232
- // session token. Sends still terminate at the HeroUI API, which owns the
7233
- // write credentials and injects signed trusted metadata.
7234
- accessToken: async ({ chatId }) => {
7235
- const response = await authorizedFetch(
7236
- `${options.apiBaseUrl}/v1/conversations/${encodeURIComponent(chatId)}/access-token`,
7237
- { method: "POST" }
7238
- );
7239
- if (!response.ok) throw new Error("Could not refresh the assistant session");
7240
- return (await response.json()).publicAccessToken;
7241
- },
7242
- baseURL: ({ endpoint }) => endpoint === "out" ? realtimeUrl : `${options.apiBaseUrl}/v1/trigger`,
7335
+ // Both directions terminate at the HeroUI API. The transport continues to
7336
+ // own parsing, resume cursors, and stop semantics, while the browser only
7337
+ // authenticates with the credential supplied by the customer's backend.
7338
+ accessToken: () => tokenManager.get(),
7339
+ baseURL: agentTransportBaseUrl(options.apiBaseUrl),
7243
7340
  clientData: modelId ? { modelId } : void 0,
7244
- fetch: async (url, init, context) => {
7245
- if (context.endpoint !== "in") return fetch(url, init);
7341
+ fetch: async (url, init) => {
7246
7342
  const headers = new Headers(init.headers);
7247
7343
  headers.delete("Authorization");
7248
7344
  return authorizedFetch(String(url), { ...init, headers });
@@ -7264,26 +7360,31 @@ function useAgentTransport({
7264
7360
  },
7265
7361
  ...initialSessions ? { sessions: initialSessions } : {},
7266
7362
  startSession: async ({ chatId, clientData }) => {
7267
- const pageContext = clientData && typeof clientData === "object" ? clientData["pageContext"] : void 0;
7268
- const requestedModelId = clientData && typeof clientData === "object" ? clientData["modelId"] : void 0;
7269
- const response = await authorizedFetch(
7270
- `${options.apiBaseUrl}/v1/conversations/${encodeURIComponent(chatId)}/session`,
7271
- {
7272
- body: JSON.stringify({
7273
- clientTools: getClientToolManifest(),
7274
- ...requestedModelId ? { modelId: requestedModelId } : {},
7275
- imageSearch: options.imageSearch,
7276
- pageContext,
7277
- protocolVersion: HEROUI_AGENT_PROTOCOL_VERSION,
7278
- sdkVersion: HEROUI_AGENT_SDK_VERSION,
7279
- webSearch: options.webSearch
7280
- }),
7281
- headers: { "Content-Type": "application/json" },
7282
- method: "POST"
7283
- }
7284
- );
7285
- if (!response.ok) throw new Error("Could not start the assistant session");
7286
- return await response.json();
7363
+ try {
7364
+ const pageContext = clientData && typeof clientData === "object" ? clientData["pageContext"] : void 0;
7365
+ const requestedModelId = clientData && typeof clientData === "object" ? clientData["modelId"] : void 0;
7366
+ const response = await authorizedFetch(
7367
+ `${options.apiBaseUrl}/v1/conversations/${encodeURIComponent(chatId)}/session`,
7368
+ {
7369
+ body: JSON.stringify({
7370
+ clientTools: getClientToolManifest(),
7371
+ ...requestedModelId ? { modelId: requestedModelId } : {},
7372
+ imageSearch: options.imageSearch,
7373
+ pageContext,
7374
+ protocolVersion: HEROUI_AGENT_PROTOCOL_VERSION,
7375
+ sdkVersion: HEROUI_AGENT_SDK_VERSION,
7376
+ webSearch: options.webSearch
7377
+ }),
7378
+ headers: { "Content-Type": "application/json" },
7379
+ method: "POST"
7380
+ }
7381
+ );
7382
+ if (!response.ok) throw new AgentSessionStartError();
7383
+ return await response.json();
7384
+ } catch (sessionError) {
7385
+ if (sessionError instanceof AgentSessionStartError) throw sessionError;
7386
+ throw new AgentSessionStartError({ cause: sessionError });
7387
+ }
7287
7388
  },
7288
7389
  task: HEROUI_AGENT_TASK_ID
7289
7390
  });