@lumerahq/ui 0.9.1 → 0.10.0

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.
Files changed (63) hide show
  1. package/dist/{RecordSheet-yAcqIdOK.js → RecordSheet-BhWMav4R.js} +3584 -2046
  2. package/dist/{automations-DNWw-HT7.js → automations-Bb9nlU05.js} +281 -22
  3. package/dist/components/agent-chat/AgentChat.d.ts +1 -1
  4. package/dist/components/agent-chat/AgentChat.d.ts.map +1 -1
  5. package/dist/components/agent-chat/ArtifactCard.d.ts +8 -0
  6. package/dist/components/agent-chat/ArtifactCard.d.ts.map +1 -0
  7. package/dist/components/agent-chat/index.d.ts +4 -0
  8. package/dist/components/agent-chat/index.d.ts.map +1 -1
  9. package/dist/components/agent-chat/tool-renderers/ArtifactRenderer.d.ts +3 -0
  10. package/dist/components/agent-chat/tool-renderers/ArtifactRenderer.d.ts.map +1 -0
  11. package/dist/components/agent-chat/tool-renderers/BashRenderer.d.ts +3 -0
  12. package/dist/components/agent-chat/tool-renderers/BashRenderer.d.ts.map +1 -0
  13. package/dist/components/agent-chat/tool-renderers/DefaultRenderer.d.ts +3 -0
  14. package/dist/components/agent-chat/tool-renderers/DefaultRenderer.d.ts.map +1 -0
  15. package/dist/components/agent-chat/tool-renderers/EditRenderer.d.ts +3 -0
  16. package/dist/components/agent-chat/tool-renderers/EditRenderer.d.ts.map +1 -0
  17. package/dist/components/agent-chat/tool-renderers/FindRenderer.d.ts +3 -0
  18. package/dist/components/agent-chat/tool-renderers/FindRenderer.d.ts.map +1 -0
  19. package/dist/components/agent-chat/tool-renderers/GrepRenderer.d.ts +3 -0
  20. package/dist/components/agent-chat/tool-renderers/GrepRenderer.d.ts.map +1 -0
  21. package/dist/components/agent-chat/tool-renderers/IntegrationProxyRenderer.d.ts +3 -0
  22. package/dist/components/agent-chat/tool-renderers/IntegrationProxyRenderer.d.ts.map +1 -0
  23. package/dist/components/agent-chat/tool-renderers/LsRenderer.d.ts +3 -0
  24. package/dist/components/agent-chat/tool-renderers/LsRenderer.d.ts.map +1 -0
  25. package/dist/components/agent-chat/tool-renderers/LumeraApiRenderer.d.ts +3 -0
  26. package/dist/components/agent-chat/tool-renderers/LumeraApiRenderer.d.ts.map +1 -0
  27. package/dist/components/agent-chat/tool-renderers/ReadRenderer.d.ts +3 -0
  28. package/dist/components/agent-chat/tool-renderers/ReadRenderer.d.ts.map +1 -0
  29. package/dist/components/agent-chat/tool-renderers/SqlQueryRenderer.d.ts +3 -0
  30. package/dist/components/agent-chat/tool-renderers/SqlQueryRenderer.d.ts.map +1 -0
  31. package/dist/components/agent-chat/tool-renderers/ToolCallCard.d.ts +15 -0
  32. package/dist/components/agent-chat/tool-renderers/ToolCallCard.d.ts.map +1 -0
  33. package/dist/components/agent-chat/tool-renderers/WebSearchRenderer.d.ts +3 -0
  34. package/dist/components/agent-chat/tool-renderers/WebSearchRenderer.d.ts.map +1 -0
  35. package/dist/components/agent-chat/tool-renderers/WriteRenderer.d.ts +3 -0
  36. package/dist/components/agent-chat/tool-renderers/WriteRenderer.d.ts.map +1 -0
  37. package/dist/components/agent-chat/tool-renderers/index.d.ts +13 -0
  38. package/dist/components/agent-chat/tool-renderers/index.d.ts.map +1 -0
  39. package/dist/components/agent-chat/tool-renderers/types.d.ts +27 -0
  40. package/dist/components/agent-chat/tool-renderers/types.d.ts.map +1 -0
  41. package/dist/components/agent-chat/tool-renderers/utils.d.ts +35 -0
  42. package/dist/components/agent-chat/tool-renderers/utils.d.ts.map +1 -0
  43. package/dist/components/agent-chat/types.d.ts +37 -0
  44. package/dist/components/agent-chat/types.d.ts.map +1 -1
  45. package/dist/components/index.d.ts +2 -2
  46. package/dist/components/index.d.ts.map +1 -1
  47. package/dist/components/index.js +15 -6
  48. package/dist/components/ui/button.d.ts +1 -1
  49. package/dist/{highlighted-body-OFNGDK62-CeR4x7F6.js → highlighted-body-OFNGDK62-C-pMiyu6.js} +1 -1
  50. package/dist/hooks/index.js +2 -2
  51. package/dist/hooks/use-agent-chat.d.ts +19 -1
  52. package/dist/hooks/use-agent-chat.d.ts.map +1 -1
  53. package/dist/hooks/use-discovery-poll.d.ts +26 -0
  54. package/dist/hooks/use-discovery-poll.d.ts.map +1 -0
  55. package/dist/index.js +64 -56
  56. package/dist/lib/index.js +52 -53
  57. package/dist/mermaid-GHXKKRXX-BGPCb6N_.js +4 -0
  58. package/dist/ui.css +135 -10
  59. package/dist/{use-automation-run-D_1647k0.js → use-automation-run-Bj17PZB_.js} +261 -13
  60. package/dist/{use-sql-table-8APCNryn.js → use-sql-table-D5tupvS2.js} +1 -2
  61. package/package.json +1 -1
  62. package/dist/api-Bm4dzr1n.js +0 -262
  63. package/dist/mermaid-GHXKKRXX-BCicmuVL.js +0 -4
package/dist/ui.css CHANGED
@@ -423,6 +423,14 @@
423
423
  pointer-events: none;
424
424
  }
425
425
 
426
+ .collapse {
427
+ visibility: collapse;
428
+ }
429
+
430
+ .visible {
431
+ visibility: visible;
432
+ }
433
+
426
434
  .sr-only {
427
435
  clip-path: inset(50%);
428
436
  white-space: nowrap;
@@ -611,6 +619,10 @@
611
619
  margin-right: calc(var(--spacing) * 2);
612
620
  }
613
621
 
622
+ .mb-1\.5 {
623
+ margin-bottom: calc(var(--spacing) * 1.5);
624
+ }
625
+
614
626
  .mb-2 {
615
627
  margin-bottom: calc(var(--spacing) * 2);
616
628
  }
@@ -668,6 +680,10 @@
668
680
  display: none;
669
681
  }
670
682
 
683
+ .inline {
684
+ display: inline;
685
+ }
686
+
671
687
  .inline-flex {
672
688
  display: inline-flex;
673
689
  }
@@ -720,6 +736,11 @@
720
736
  height: calc(var(--spacing) * 4);
721
737
  }
722
738
 
739
+ .size-5 {
740
+ width: calc(var(--spacing) * 5);
741
+ height: calc(var(--spacing) * 5);
742
+ }
743
+
723
744
  .size-6 {
724
745
  width: calc(var(--spacing) * 6);
725
746
  height: calc(var(--spacing) * 6);
@@ -799,6 +820,14 @@
799
820
  height: calc(var(--spacing) * 24);
800
821
  }
801
822
 
823
+ .h-56 {
824
+ height: calc(var(--spacing) * 56);
825
+ }
826
+
827
+ .h-\[500px\] {
828
+ height: 500px;
829
+ }
830
+
802
831
  .h-\[720px\] {
803
832
  height: 720px;
804
833
  }
@@ -835,14 +864,18 @@
835
864
  max-height: calc(var(--spacing) * 48);
836
865
  }
837
866
 
838
- .max-h-56 {
839
- max-height: calc(var(--spacing) * 56);
840
- }
841
-
842
867
  .max-h-60 {
843
868
  max-height: calc(var(--spacing) * 60);
844
869
  }
845
870
 
871
+ .max-h-\[200px\] {
872
+ max-height: 200px;
873
+ }
874
+
875
+ .max-h-\[600px\] {
876
+ max-height: 600px;
877
+ }
878
+
846
879
  .max-h-\[calc\(100vh-2rem\)\] {
847
880
  max-height: calc(100vh - 2rem);
848
881
  }
@@ -963,6 +996,10 @@
963
996
  max-width: 88%;
964
997
  }
965
998
 
999
+ .max-w-full {
1000
+ max-width: 100%;
1001
+ }
1002
+
966
1003
  .max-w-lg {
967
1004
  max-width: var(--container-lg);
968
1005
  }
@@ -1072,6 +1109,10 @@
1072
1109
  cursor: default;
1073
1110
  }
1074
1111
 
1112
+ .cursor-not-allowed {
1113
+ cursor: not-allowed;
1114
+ }
1115
+
1075
1116
  .cursor-pointer {
1076
1117
  cursor: pointer;
1077
1118
  }
@@ -1208,12 +1249,22 @@
1208
1249
  gap: calc(var(--spacing) * 8);
1209
1250
  }
1210
1251
 
1252
+ .gap-px {
1253
+ gap: 1px;
1254
+ }
1255
+
1211
1256
  :where(.space-y-1 > :not(:last-child)) {
1212
1257
  --tw-space-y-reverse: 0;
1213
1258
  margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
1214
1259
  margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
1215
1260
  }
1216
1261
 
1262
+ :where(.space-y-1\.5 > :not(:last-child)) {
1263
+ --tw-space-y-reverse: 0;
1264
+ margin-block-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));
1265
+ margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)));
1266
+ }
1267
+
1217
1268
  :where(.space-y-2 > :not(:last-child)) {
1218
1269
  --tw-space-y-reverse: 0;
1219
1270
  margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
@@ -1502,13 +1553,13 @@
1502
1553
  }
1503
1554
  }
1504
1555
 
1505
- .bg-muted, .bg-muted\/30 {
1556
+ .bg-muted, .bg-muted\/20 {
1506
1557
  background-color: var(--muted);
1507
1558
  }
1508
1559
 
1509
1560
  @supports (color: color-mix(in lab, red, red)) {
1510
- .bg-muted\/30 {
1511
- background-color: color-mix(in oklab, var(--muted) 30%, transparent);
1561
+ .bg-muted\/20 {
1562
+ background-color: color-mix(in oklab, var(--muted) 20%, transparent);
1512
1563
  }
1513
1564
  }
1514
1565
 
@@ -1532,14 +1583,30 @@
1532
1583
  }
1533
1584
  }
1534
1585
 
1586
+ .bg-muted\/60 {
1587
+ background-color: var(--muted);
1588
+ }
1589
+
1590
+ @supports (color: color-mix(in lab, red, red)) {
1591
+ .bg-muted\/60 {
1592
+ background-color: color-mix(in oklab, var(--muted) 60%, transparent);
1593
+ }
1594
+ }
1595
+
1535
1596
  .bg-popover {
1536
1597
  background-color: var(--popover);
1537
1598
  }
1538
1599
 
1539
- .bg-primary {
1600
+ .bg-primary, .bg-primary\/10 {
1540
1601
  background-color: var(--primary);
1541
1602
  }
1542
1603
 
1604
+ @supports (color: color-mix(in lab, red, red)) {
1605
+ .bg-primary\/10 {
1606
+ background-color: color-mix(in oklab, var(--primary) 10%, transparent);
1607
+ }
1608
+ }
1609
+
1543
1610
  .bg-red-50 {
1544
1611
  background-color: var(--color-red-50);
1545
1612
  }
@@ -1580,6 +1647,10 @@
1580
1647
  background-color: #0000;
1581
1648
  }
1582
1649
 
1650
+ .bg-white {
1651
+ background-color: var(--color-white);
1652
+ }
1653
+
1583
1654
  .bg-clip-padding {
1584
1655
  background-clip: padding-box;
1585
1656
  }
@@ -1588,6 +1659,10 @@
1588
1659
  fill: var(--foreground);
1589
1660
  }
1590
1661
 
1662
+ .object-contain {
1663
+ object-fit: contain;
1664
+ }
1665
+
1591
1666
  .object-cover {
1592
1667
  object-fit: cover;
1593
1668
  }
@@ -1604,6 +1679,10 @@
1604
1679
  padding: calc(var(--spacing) * 2);
1605
1680
  }
1606
1681
 
1682
+ .p-2\.5 {
1683
+ padding: calc(var(--spacing) * 2.5);
1684
+ }
1685
+
1607
1686
  .p-3 {
1608
1687
  padding: calc(var(--spacing) * 3);
1609
1688
  }
@@ -1786,6 +1865,10 @@
1786
1865
  font-size: 11px;
1787
1866
  }
1788
1867
 
1868
+ .text-\[12px\] {
1869
+ font-size: 12px;
1870
+ }
1871
+
1789
1872
  .leading-none {
1790
1873
  --tw-leading: 1;
1791
1874
  line-height: 1;
@@ -1835,6 +1918,10 @@
1835
1918
  text-wrap: balance;
1836
1919
  }
1837
1920
 
1921
+ .break-words {
1922
+ overflow-wrap: break-word;
1923
+ }
1924
+
1838
1925
  .whitespace-nowrap {
1839
1926
  white-space: nowrap;
1840
1927
  }
@@ -1871,18 +1958,40 @@
1871
1958
  color: var(--color-emerald-800);
1872
1959
  }
1873
1960
 
1874
- .text-foreground {
1961
+ .text-foreground, .text-foreground\/80 {
1875
1962
  color: var(--foreground);
1876
1963
  }
1877
1964
 
1965
+ @supports (color: color-mix(in lab, red, red)) {
1966
+ .text-foreground\/80 {
1967
+ color: color-mix(in oklab, var(--foreground) 80%, transparent);
1968
+ }
1969
+ }
1970
+
1878
1971
  .text-green-600 {
1879
1972
  color: var(--color-green-600);
1880
1973
  }
1881
1974
 
1882
- .text-muted-foreground {
1975
+ .text-muted-foreground, .text-muted-foreground\/50 {
1976
+ color: var(--muted-foreground);
1977
+ }
1978
+
1979
+ @supports (color: color-mix(in lab, red, red)) {
1980
+ .text-muted-foreground\/50 {
1981
+ color: color-mix(in oklab, var(--muted-foreground) 50%, transparent);
1982
+ }
1983
+ }
1984
+
1985
+ .text-muted-foreground\/60 {
1883
1986
  color: var(--muted-foreground);
1884
1987
  }
1885
1988
 
1989
+ @supports (color: color-mix(in lab, red, red)) {
1990
+ .text-muted-foreground\/60 {
1991
+ color: color-mix(in oklab, var(--muted-foreground) 60%, transparent);
1992
+ }
1993
+ }
1994
+
1886
1995
  .text-popover-foreground {
1887
1996
  color: var(--popover-foreground);
1888
1997
  }
@@ -1942,14 +2051,26 @@
1942
2051
  font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
1943
2052
  }
1944
2053
 
2054
+ .underline {
2055
+ text-decoration-line: underline;
2056
+ }
2057
+
1945
2058
  .underline-offset-4 {
1946
2059
  text-underline-offset: 4px;
1947
2060
  }
1948
2061
 
2062
+ .opacity-0 {
2063
+ opacity: 0;
2064
+ }
2065
+
1949
2066
  .opacity-30 {
1950
2067
  opacity: .3;
1951
2068
  }
1952
2069
 
2070
+ .opacity-40 {
2071
+ opacity: .4;
2072
+ }
2073
+
1953
2074
  .opacity-50 {
1954
2075
  opacity: .5;
1955
2076
  }
@@ -1958,6 +2079,10 @@
1958
2079
  opacity: .8;
1959
2080
  }
1960
2081
 
2082
+ .opacity-100 {
2083
+ opacity: 1;
2084
+ }
2085
+
1961
2086
  .mix-blend-color {
1962
2087
  mix-blend-mode: color;
1963
2088
  }
@@ -1,6 +1,90 @@
1
- import { useState, useRef, useCallback, useEffect, useMemo } from "react";
1
+ import { useEffect, useState, useRef, useCallback, useMemo } from "react";
2
2
  import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
3
- import { n as ensureAutomationRun, l as createAutomationRun, h as cancelAutomationRun, f as automationStatuses, t as getAutomationRun } from "./automations-DNWw-HT7.js";
3
+ import { H as ensureAutomationRun, F as createAutomationRun, A as cancelAutomationRun, z as automationStatuses, K as getAutomationRun } from "./automations-Bb9nlU05.js";
4
+ const QUEUED_DISCOVERY_POLL_MS = 2e3;
5
+ const IDLE_DISCOVERY_POLL_MS = 1e4;
6
+ const HIDDEN_DISCOVERY_POLL_MS = 3e4;
7
+ function isDocumentHidden() {
8
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
9
+ }
10
+ function discoveryPollDelayMs({
11
+ hasQueuedPending,
12
+ hidden
13
+ }) {
14
+ if (hidden) return HIDDEN_DISCOVERY_POLL_MS;
15
+ if (hasQueuedPending) return QUEUED_DISCOVERY_POLL_MS;
16
+ return IDLE_DISCOVERY_POLL_MS;
17
+ }
18
+ function useDiscoveryPoll({
19
+ enabled,
20
+ isTransportActive,
21
+ isRefreshInFlight,
22
+ hasQueuedPending,
23
+ refresh
24
+ }) {
25
+ useEffect(() => {
26
+ if (!enabled || typeof window === "undefined") {
27
+ return;
28
+ }
29
+ let timer = null;
30
+ let disposed = false;
31
+ const clearTimer = () => {
32
+ if (timer != null) {
33
+ window.clearTimeout(timer);
34
+ timer = null;
35
+ }
36
+ };
37
+ const schedule = () => {
38
+ if (disposed) return;
39
+ clearTimer();
40
+ timer = window.setTimeout(
41
+ () => {
42
+ timer = null;
43
+ if (isTransportActive() || isRefreshInFlight()) {
44
+ schedule();
45
+ return;
46
+ }
47
+ void refresh().finally(() => {
48
+ schedule();
49
+ });
50
+ },
51
+ discoveryPollDelayMs({
52
+ hidden: isDocumentHidden(),
53
+ hasQueuedPending: hasQueuedPending()
54
+ })
55
+ );
56
+ };
57
+ const refreshNow = () => {
58
+ if (disposed || isTransportActive() || isRefreshInFlight() || isDocumentHidden()) {
59
+ schedule();
60
+ return;
61
+ }
62
+ clearTimer();
63
+ void refresh().finally(() => {
64
+ schedule();
65
+ });
66
+ };
67
+ const onVisibilityChange = () => {
68
+ if (isDocumentHidden()) {
69
+ schedule();
70
+ return;
71
+ }
72
+ refreshNow();
73
+ };
74
+ const onFocus = () => {
75
+ refreshNow();
76
+ };
77
+ schedule();
78
+ document.addEventListener("visibilitychange", onVisibilityChange);
79
+ window.addEventListener("focus", onFocus);
80
+ return () => {
81
+ disposed = true;
82
+ clearTimer();
83
+ document.removeEventListener("visibilitychange", onVisibilityChange);
84
+ window.removeEventListener("focus", onFocus);
85
+ };
86
+ }, [enabled, hasQueuedPending, isRefreshInFlight, isTransportActive, refresh]);
87
+ }
4
88
  const DEFAULT_SESSION_ID = "default";
5
89
  const DEFAULT_HISTORY_LIMIT = 10;
6
90
  const DEFAULT_MAX_BUFFERED_MESSAGES = 100;
@@ -17,6 +101,10 @@ function useAgentChat({
17
101
  disabled = false,
18
102
  autoLoadHistory = true,
19
103
  autoSubscribe = true,
104
+ watchdog = false,
105
+ watchdogTimeoutMs = 3e4,
106
+ watchdogPollMs = 1e3,
107
+ discoveryPoll = false,
20
108
  idFactory = defaultIDFactory,
21
109
  onMessagesChange,
22
110
  onTurnComplete,
@@ -35,10 +123,18 @@ function useAgentChat({
35
123
  const [isSubmitting, setIsSubmitting] = useState(false);
36
124
  const [isStreaming, setIsStreaming] = useState(false);
37
125
  const [isAborting, setIsAborting] = useState(false);
126
+ const [historyLoaded, setHistoryLoaded] = useState(false);
38
127
  const messagesRef = useRef(messages);
39
128
  const subscriptionRef = useRef(null);
40
129
  const activeRequestIdRef = useRef(void 0);
41
130
  const mountedRef = useRef(true);
131
+ const refreshInFlightRef = useRef(false);
132
+ const isStreamingRef = useRef(false);
133
+ const isSubmittingRef = useRef(false);
134
+ const isAbortingRef = useRef(false);
135
+ const isLoadingHistoryRef = useRef(false);
136
+ const watchdogTimerRef = useRef(null);
137
+ const identityKeyRef = useRef(`${agentId ?? ""}::${sessionId}`);
42
138
  const setMessages = useCallback(
43
139
  (updater) => {
44
140
  setMessagesState((current) => {
@@ -57,8 +153,19 @@ function useAgentChat({
57
153
  mountedRef.current = false;
58
154
  subscriptionRef.current?.close();
59
155
  subscriptionRef.current = null;
156
+ if (watchdogTimerRef.current != null) {
157
+ clearTimeout(watchdogTimerRef.current);
158
+ watchdogTimerRef.current = null;
159
+ }
60
160
  };
61
161
  }, []);
162
+ useEffect(() => {
163
+ isStreamingRef.current = isStreaming;
164
+ isSubmittingRef.current = isSubmitting;
165
+ isAbortingRef.current = isAborting;
166
+ isLoadingHistoryRef.current = isLoadingHistory;
167
+ identityKeyRef.current = `${agentId ?? ""}::${sessionId}`;
168
+ });
62
169
  const handleError = useCallback(
63
170
  (err) => {
64
171
  if (!mountedRef.current) return;
@@ -77,21 +184,96 @@ function useAgentChat({
77
184
  );
78
185
  const loadHistory = useCallback(async () => {
79
186
  if (disabled) return;
187
+ const requestedIdentity = `${agentId ?? ""}::${sessionId}`;
80
188
  setIsLoadingHistory(true);
81
189
  setError(null);
82
190
  try {
83
191
  const result = await transport.loadHistory({ agentId, sessionId, limit: historyLimit });
84
- if (!mountedRef.current) return;
192
+ if (!mountedRef.current || identityKeyRef.current !== requestedIdentity) return;
85
193
  applyHistoryResult(result);
194
+ setHistoryLoaded(true);
86
195
  } catch (err) {
196
+ if (identityKeyRef.current !== requestedIdentity) return;
87
197
  handleError(err);
88
198
  } finally {
89
- if (mountedRef.current) setIsLoadingHistory(false);
199
+ if (mountedRef.current && identityKeyRef.current === requestedIdentity) setIsLoadingHistory(false);
90
200
  }
91
201
  }, [agentId, applyHistoryResult, disabled, handleError, historyLimit, sessionId, transport]);
202
+ const clearWatchdog = useCallback(() => {
203
+ if (watchdogTimerRef.current != null) {
204
+ clearTimeout(watchdogTimerRef.current);
205
+ watchdogTimerRef.current = null;
206
+ }
207
+ }, []);
208
+ const requestStillActive = useCallback((requestId) => {
209
+ return messagesRef.current.some(
210
+ (message) => message.role === "user" && message.requestId === requestId && isActiveStatus(message.status)
211
+ );
212
+ }, []);
213
+ const reconcileFromHistory = useCallback(async () => {
214
+ if (disabled || refreshInFlightRef.current) return;
215
+ const requestedIdentity = `${agentId ?? ""}::${sessionId}`;
216
+ refreshInFlightRef.current = true;
217
+ try {
218
+ const result = await transport.loadHistory({ agentId, sessionId, limit: historyLimit });
219
+ if (!mountedRef.current || identityKeyRef.current !== requestedIdentity) return;
220
+ applyHistoryResult(result);
221
+ setHistoryLoaded(true);
222
+ } catch {
223
+ } finally {
224
+ refreshInFlightRef.current = false;
225
+ }
226
+ }, [agentId, applyHistoryResult, disabled, historyLimit, sessionId, transport]);
227
+ const settleStaleRequest = useCallback(
228
+ (requestId) => {
229
+ setMessages(
230
+ (current) => current.map(
231
+ (message) => message.role === "user" && message.requestId === requestId && isActiveStatus(message.status) ? { ...message, status: "interrupted" } : message
232
+ )
233
+ );
234
+ handleError(new Error("Lost connection to the agent. The response may still be processing."));
235
+ },
236
+ [handleError, setMessages]
237
+ );
238
+ const startWatchdog = useCallback(
239
+ (requestId) => {
240
+ if (!watchdog) return;
241
+ clearWatchdog();
242
+ const startedAt = Date.now();
243
+ const tick = () => {
244
+ watchdogTimerRef.current = window.setTimeout(async () => {
245
+ watchdogTimerRef.current = null;
246
+ if (!mountedRef.current) return;
247
+ if (!requestStillActive(requestId)) return;
248
+ if (Date.now() - startedAt >= watchdogTimeoutMs) {
249
+ settleStaleRequest(requestId);
250
+ return;
251
+ }
252
+ await reconcileFromHistory();
253
+ if (!mountedRef.current || !requestStillActive(requestId)) return;
254
+ if (Date.now() - startedAt >= watchdogTimeoutMs) {
255
+ settleStaleRequest(requestId);
256
+ return;
257
+ }
258
+ tick();
259
+ }, watchdogPollMs);
260
+ };
261
+ tick();
262
+ },
263
+ [
264
+ clearWatchdog,
265
+ reconcileFromHistory,
266
+ requestStillActive,
267
+ settleStaleRequest,
268
+ watchdog,
269
+ watchdogPollMs,
270
+ watchdogTimeoutMs
271
+ ]
272
+ );
92
273
  const loadEarlier = useCallback(async () => {
93
274
  if (disabled || isLoadingEarlier || !page?.hasMoreBefore || !page.beforeCursor) return;
94
275
  const loadEarlierImpl = transport.loadEarlier ?? transport.loadHistory;
276
+ const requestedIdentity = `${agentId ?? ""}::${sessionId}`;
95
277
  setIsLoadingEarlier(true);
96
278
  setError(null);
97
279
  try {
@@ -101,19 +283,21 @@ function useAgentChat({
101
283
  beforeCursor: page.beforeCursor,
102
284
  limit: historyLimit
103
285
  });
104
- if (!mountedRef.current) return;
286
+ if (!mountedRef.current || identityKeyRef.current !== requestedIdentity) return;
105
287
  const earlier = mergePendingMessages(result.messages, result.pending ?? []);
106
288
  setMessages((current) => mergeUniqueMessages([...earlier, ...current]));
107
289
  setPage(result.page ?? page);
108
290
  } catch (err) {
291
+ if (identityKeyRef.current !== requestedIdentity) return;
109
292
  handleError(err);
110
293
  } finally {
111
- if (mountedRef.current) setIsLoadingEarlier(false);
294
+ if (mountedRef.current && identityKeyRef.current === requestedIdentity) setIsLoadingEarlier(false);
112
295
  }
113
296
  }, [agentId, disabled, handleError, historyLimit, isLoadingEarlier, page, sessionId, setMessages, transport]);
114
297
  const settleRequest = useCallback(
115
298
  (requestId, status, errorMessage) => {
116
299
  if (!requestId) return;
300
+ clearWatchdog();
117
301
  setMessages(
118
302
  (current) => current.map(
119
303
  (messageItem) => messageItem.role === "user" && messageItem.requestId === requestId ? { ...messageItem, status, error: errorMessage ?? messageItem.error } : messageItem
@@ -125,7 +309,7 @@ function useAgentChat({
125
309
  if (!transport.subscribe) return;
126
310
  void loadHistory();
127
311
  },
128
- [loadHistory, onTurnComplete, setMessages, transport.subscribe]
312
+ [clearWatchdog, loadHistory, onTurnComplete, setMessages, transport.subscribe]
129
313
  );
130
314
  const applyStreamEvent = useCallback(
131
315
  (event) => {
@@ -189,6 +373,7 @@ function useAgentChat({
189
373
  (requestId) => {
190
374
  if (!autoSubscribe || !transport.subscribe || !requestId) return;
191
375
  subscriptionRef.current?.close();
376
+ clearWatchdog();
192
377
  activeRequestIdRef.current = requestId;
193
378
  setIsStreaming(true);
194
379
  subscriptionRef.current = transport.subscribe(
@@ -196,18 +381,36 @@ function useAgentChat({
196
381
  {
197
382
  onEvent: applyStreamEvent,
198
383
  onError: (err) => {
199
- handleError(err);
200
- if (mountedRef.current) setIsStreaming(false);
384
+ if (!mountedRef.current) return;
385
+ setIsStreaming(false);
386
+ if (watchdog && activeRequestIdRef.current === requestId && requestStillActive(requestId)) {
387
+ startWatchdog(requestId);
388
+ } else {
389
+ handleError(err);
390
+ }
201
391
  },
202
392
  onClose: () => {
203
- if (mountedRef.current && activeRequestIdRef.current === requestId) {
204
- setIsStreaming(false);
393
+ if (!mountedRef.current || activeRequestIdRef.current !== requestId) return;
394
+ setIsStreaming(false);
395
+ if (watchdog && requestStillActive(requestId)) {
396
+ startWatchdog(requestId);
205
397
  }
206
398
  }
207
399
  }
208
400
  );
209
401
  },
210
- [agentId, applyStreamEvent, autoSubscribe, handleError, sessionId, transport]
402
+ [
403
+ agentId,
404
+ applyStreamEvent,
405
+ autoSubscribe,
406
+ clearWatchdog,
407
+ handleError,
408
+ requestStillActive,
409
+ sessionId,
410
+ startWatchdog,
411
+ transport,
412
+ watchdog
413
+ ]
211
414
  );
212
415
  const sendMessage = useCallback(
213
416
  async (message) => {
@@ -272,6 +475,7 @@ function useAgentChat({
272
475
  await transport.abort?.({ agentId, sessionId, requestId });
273
476
  subscriptionRef.current?.close();
274
477
  subscriptionRef.current = null;
478
+ clearWatchdog();
275
479
  setIsStreaming(false);
276
480
  setMessages(
277
481
  (current) => current.map(
@@ -283,7 +487,7 @@ function useAgentChat({
283
487
  } finally {
284
488
  if (mountedRef.current) setIsAborting(false);
285
489
  }
286
- }, [agentId, disabled, handleError, isAborting, sessionId, setMessages, transport]);
490
+ }, [agentId, clearWatchdog, disabled, handleError, isAborting, sessionId, setMessages, transport]);
287
491
  const attachFiles = useCallback(
288
492
  async (files) => {
289
493
  if (files.length === 0) return;
@@ -327,6 +531,26 @@ function useAgentChat({
327
531
  const clearPendingFiles = useCallback(() => {
328
532
  setPendingFiles([]);
329
533
  }, []);
534
+ const identityKey = `${agentId ?? ""}::${sessionId}`;
535
+ const previousIdentityRef = useRef(identityKey);
536
+ useEffect(() => {
537
+ if (previousIdentityRef.current === identityKey) return;
538
+ previousIdentityRef.current = identityKey;
539
+ subscriptionRef.current?.close();
540
+ subscriptionRef.current = null;
541
+ clearWatchdog();
542
+ activeRequestIdRef.current = void 0;
543
+ setMessages([]);
544
+ setPage(initialPage ?? null);
545
+ setTokenUsage(null);
546
+ setError(null);
547
+ setIsLoadingHistory(false);
548
+ setIsLoadingEarlier(false);
549
+ setIsStreaming(false);
550
+ setIsSubmitting(false);
551
+ setIsAborting(false);
552
+ setHistoryLoaded(false);
553
+ }, [identityKey, clearWatchdog, initialPage, setMessages]);
330
554
  useEffect(() => {
331
555
  if (!autoLoadHistory) return;
332
556
  void loadHistory();
@@ -342,6 +566,22 @@ function useAgentChat({
342
566
  const canSubmit = !disabled && !isSubmitting && !hasUploadingFiles && !hasFailedUploads && (input.trim().length > 0 || pendingFiles.length > 0) && (!hasActive || queueLength < maxQueuedMessages);
343
567
  const canAbort = !disabled && !isAborting && hasActive;
344
568
  const phase = derivePhase({ isLoadingHistory, isLoadingEarlier, isSubmitting, isStreaming, isAborting, error });
569
+ const isTransportActiveCb = useCallback(
570
+ () => isStreamingRef.current || isSubmittingRef.current || isAbortingRef.current,
571
+ []
572
+ );
573
+ const isRefreshInFlightCb = useCallback(() => isLoadingHistoryRef.current || refreshInFlightRef.current, []);
574
+ const hasQueuedPendingCb = useCallback(
575
+ () => messagesRef.current.some((message) => message.role === "user" && message.status === "queued"),
576
+ []
577
+ );
578
+ useDiscoveryPoll({
579
+ enabled: discoveryPoll && autoLoadHistory && historyLoaded && !disabled,
580
+ isTransportActive: isTransportActiveCb,
581
+ isRefreshInFlight: isRefreshInFlightCb,
582
+ hasQueuedPending: hasQueuedPendingCb,
583
+ refresh: reconcileFromHistory
584
+ });
345
585
  return {
346
586
  messages,
347
587
  turns,
@@ -593,8 +833,16 @@ function buildAgentChatTurns(messages) {
593
833
  turns.push({ id: message.requestId ?? message.id, requestId: message.requestId, assistants: [message] });
594
834
  }
595
835
  }
836
+ turns.sort((a, b) => turnRank(a) - turnRank(b));
837
+ const first = turns[0];
838
+ if (turns.length > 1 && first && !first.user && (first.systems?.length ?? 0) === 0 && first.assistants.length > 0) {
839
+ return turns.slice(1);
840
+ }
596
841
  return turns;
597
842
  }
843
+ function turnRank(turn) {
844
+ return turn.user?.status === "queued" ? 1 : 0;
845
+ }
598
846
  function accumulateTokenUsage(current, incoming) {
599
847
  if (!current) return incoming;
600
848
  return {