@flamingo-stack/openframe-frontend-core 0.0.443 → 0.0.444-snapshot.20260717182533

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 (130) hide show
  1. package/dist/{chunk-DOVBNECK.js → chunk-22NSWMI6.js} +3 -3
  2. package/dist/{chunk-NQODKJ2F.js → chunk-3BHV2QAH.js} +561 -145
  3. package/dist/chunk-3BHV2QAH.js.map +1 -0
  4. package/dist/{chunk-3ZE55IH6.js → chunk-3DQ5BFPM.js} +5 -5
  5. package/dist/{chunk-KNNSS3CZ.cjs → chunk-3HYSD7NR.cjs} +15 -15
  6. package/dist/{chunk-KNNSS3CZ.cjs.map → chunk-3HYSD7NR.cjs.map} +1 -1
  7. package/dist/{chunk-ZVAAIP5Y.cjs → chunk-5DWPXK37.cjs} +40 -40
  8. package/dist/{chunk-ZVAAIP5Y.cjs.map → chunk-5DWPXK37.cjs.map} +1 -1
  9. package/dist/{chunk-323TCNF3.cjs → chunk-5FUQTSTT.cjs} +18 -18
  10. package/dist/{chunk-323TCNF3.cjs.map → chunk-5FUQTSTT.cjs.map} +1 -1
  11. package/dist/{chunk-MG4AMIFX.js → chunk-5NYXGWBN.js} +2 -2
  12. package/dist/{chunk-UXJ47OJF.js → chunk-7R256DKK.js} +2 -2
  13. package/dist/{chunk-OHKKI6EF.cjs → chunk-DTLNCOSG.cjs} +32 -32
  14. package/dist/{chunk-OHKKI6EF.cjs.map → chunk-DTLNCOSG.cjs.map} +1 -1
  15. package/dist/{chunk-LYYVTI34.js → chunk-FIAXFNAA.js} +5 -5
  16. package/dist/{chunk-FQASXKBK.js → chunk-G2KXNMFQ.js} +2 -2
  17. package/dist/{chunk-RQVR7I73.js → chunk-H2DYCT6M.js} +2 -2
  18. package/dist/{chunk-6IPNXMB7.js → chunk-H352AZJN.js} +6 -6
  19. package/dist/{chunk-A7MG4SBL.js → chunk-K4RASNQM.js} +3 -3
  20. package/dist/{chunk-PRJFSF4W.cjs → chunk-KISQUH33.cjs} +73 -73
  21. package/dist/{chunk-PRJFSF4W.cjs.map → chunk-KISQUH33.cjs.map} +1 -1
  22. package/dist/{chunk-M6XUXQ5Q.cjs → chunk-MPSA6YAM.cjs} +40 -40
  23. package/dist/{chunk-M6XUXQ5Q.cjs.map → chunk-MPSA6YAM.cjs.map} +1 -1
  24. package/dist/{chunk-PVMPBWKX.js → chunk-MQUS6BFR.js} +6 -6
  25. package/dist/{chunk-H4W3NCR2.cjs → chunk-N64VTHZ5.cjs} +7 -7
  26. package/dist/{chunk-H4W3NCR2.cjs.map → chunk-N64VTHZ5.cjs.map} +1 -1
  27. package/dist/{chunk-VACXEUGF.cjs → chunk-P3FN4NAE.cjs} +29 -29
  28. package/dist/{chunk-VACXEUGF.cjs.map → chunk-P3FN4NAE.cjs.map} +1 -1
  29. package/dist/{chunk-PHWQLKVE.js → chunk-P47NN7RY.js} +29 -31
  30. package/dist/chunk-P47NN7RY.js.map +1 -0
  31. package/dist/{chunk-2CJ734N2.cjs → chunk-PTT4J5GF.cjs} +3 -3
  32. package/dist/{chunk-2CJ734N2.cjs.map → chunk-PTT4J5GF.cjs.map} +1 -1
  33. package/dist/{chunk-IBIGXER5.cjs → chunk-QL2EI56X.cjs} +803 -387
  34. package/dist/chunk-QL2EI56X.cjs.map +1 -0
  35. package/dist/{chunk-YPFQ2UIH.cjs → chunk-R6WGCJBB.cjs} +73 -73
  36. package/dist/{chunk-YPFQ2UIH.cjs.map → chunk-R6WGCJBB.cjs.map} +1 -1
  37. package/dist/{chunk-6NO4YYU7.cjs → chunk-RXLMN6B5.cjs} +12 -12
  38. package/dist/{chunk-6NO4YYU7.cjs.map → chunk-RXLMN6B5.cjs.map} +1 -1
  39. package/dist/{chunk-AZQOX2RS.js → chunk-TCEBFMLQ.js} +2 -2
  40. package/dist/{chunk-7CZ4MJSI.cjs → chunk-VIJARDBM.cjs} +3 -3
  41. package/dist/{chunk-7CZ4MJSI.cjs.map → chunk-VIJARDBM.cjs.map} +1 -1
  42. package/dist/{chunk-PXXS27EE.cjs → chunk-W24U7ZUV.cjs} +40 -42
  43. package/dist/chunk-W24U7ZUV.cjs.map +1 -0
  44. package/dist/{chunk-5U6OZ5FD.js → chunk-XPZLIONZ.js} +4 -4
  45. package/dist/{chunk-RYKANQE6.cjs → chunk-YCLSV2H6.cjs} +5 -5
  46. package/dist/{chunk-RYKANQE6.cjs.map → chunk-YCLSV2H6.cjs.map} +1 -1
  47. package/dist/{chunk-LGMRNYTX.js → chunk-Z4N7XLFG.js} +3 -3
  48. package/dist/components/case-studies/index.cjs +10 -10
  49. package/dist/components/case-studies/index.js +4 -4
  50. package/dist/components/chat/hooks/use-chunk-catchup.d.ts.map +1 -1
  51. package/dist/components/chat/hooks/use-jetstream-dialog-subscription.d.ts.map +1 -1
  52. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +44 -1
  53. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  54. package/dist/components/chat/hooks/use-nats-dialog-subscription.d.ts.map +1 -1
  55. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  56. package/dist/components/chat/index.cjs +10 -4
  57. package/dist/components/chat/index.cjs.map +1 -1
  58. package/dist/components/chat/index.js +9 -3
  59. package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
  60. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  61. package/dist/components/contact/index.cjs +5 -5
  62. package/dist/components/contact/index.js +4 -4
  63. package/dist/components/docs/index.cjs +7 -7
  64. package/dist/components/docs/index.js +6 -6
  65. package/dist/components/embeds/index.cjs +5 -5
  66. package/dist/components/embeds/index.js +4 -4
  67. package/dist/components/faq/index.cjs +6 -6
  68. package/dist/components/faq/index.js +5 -5
  69. package/dist/components/features/index.cjs +4 -4
  70. package/dist/components/features/index.js +3 -3
  71. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  72. package/dist/components/help-center-pages/index.cjs +23 -23
  73. package/dist/components/help-center-pages/index.js +14 -14
  74. package/dist/components/index.cjs +142 -136
  75. package/dist/components/index.cjs.map +1 -1
  76. package/dist/components/index.js +18 -12
  77. package/dist/components/index.js.map +1 -1
  78. package/dist/components/navigation/index.cjs +4 -4
  79. package/dist/components/navigation/index.js +3 -3
  80. package/dist/components/onboarding-guides/index.cjs +8 -8
  81. package/dist/components/onboarding-guides/index.js +7 -7
  82. package/dist/components/related-content/index.cjs +6 -6
  83. package/dist/components/related-content/index.js +5 -5
  84. package/dist/components/tickets/index.cjs +7 -7
  85. package/dist/components/tickets/index.js +6 -6
  86. package/dist/components/ui/checkbox-block.d.ts +6 -0
  87. package/dist/components/ui/checkbox-block.d.ts.map +1 -1
  88. package/dist/components/ui/index.cjs +10 -4
  89. package/dist/components/ui/index.cjs.map +1 -1
  90. package/dist/components/ui/index.js +9 -3
  91. package/dist/hooks/index.cjs +3 -3
  92. package/dist/hooks/index.js +2 -2
  93. package/dist/index.cjs +10 -4
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.js +9 -3
  96. package/dist/nats/index.cjs +2 -2
  97. package/dist/nats/index.js +1 -1
  98. package/dist/nats/shared-connection.d.ts +6 -0
  99. package/dist/nats/shared-connection.d.ts.map +1 -1
  100. package/package.json +1 -1
  101. package/src/components/chat/hooks/__tests__/nats-adapter-message-updaters.test.ts +270 -0
  102. package/src/components/chat/hooks/use-chunk-catchup.ts +212 -57
  103. package/src/components/chat/hooks/use-jetstream-dialog-subscription.ts +12 -8
  104. package/src/components/chat/hooks/use-nats-chat-adapter.ts +574 -9
  105. package/src/components/chat/hooks/use-nats-dialog-subscription.ts +12 -6
  106. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +40 -3
  107. package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +62 -0
  108. package/src/components/chat/utils/message-segment-accumulator.ts +6 -0
  109. package/src/components/chat/utils/process-historical-messages.ts +25 -3
  110. package/src/components/features/policy-configuration-panel.tsx +1 -4
  111. package/src/components/ui/checkbox-block.tsx +12 -1
  112. package/src/nats/shared-connection.ts +42 -29
  113. package/src/stories/CheckboxBlock.stories.tsx +18 -0
  114. package/dist/chunk-IBIGXER5.cjs.map +0 -1
  115. package/dist/chunk-NQODKJ2F.js.map +0 -1
  116. package/dist/chunk-PHWQLKVE.js.map +0 -1
  117. package/dist/chunk-PXXS27EE.cjs.map +0 -1
  118. /package/dist/{chunk-DOVBNECK.js.map → chunk-22NSWMI6.js.map} +0 -0
  119. /package/dist/{chunk-3ZE55IH6.js.map → chunk-3DQ5BFPM.js.map} +0 -0
  120. /package/dist/{chunk-MG4AMIFX.js.map → chunk-5NYXGWBN.js.map} +0 -0
  121. /package/dist/{chunk-UXJ47OJF.js.map → chunk-7R256DKK.js.map} +0 -0
  122. /package/dist/{chunk-LYYVTI34.js.map → chunk-FIAXFNAA.js.map} +0 -0
  123. /package/dist/{chunk-FQASXKBK.js.map → chunk-G2KXNMFQ.js.map} +0 -0
  124. /package/dist/{chunk-RQVR7I73.js.map → chunk-H2DYCT6M.js.map} +0 -0
  125. /package/dist/{chunk-6IPNXMB7.js.map → chunk-H352AZJN.js.map} +0 -0
  126. /package/dist/{chunk-A7MG4SBL.js.map → chunk-K4RASNQM.js.map} +0 -0
  127. /package/dist/{chunk-PVMPBWKX.js.map → chunk-MQUS6BFR.js.map} +0 -0
  128. /package/dist/{chunk-AZQOX2RS.js.map → chunk-TCEBFMLQ.js.map} +0 -0
  129. /package/dist/{chunk-5U6OZ5FD.js.map → chunk-XPZLIONZ.js.map} +0 -0
  130. /package/dist/{chunk-LGMRNYTX.js.map → chunk-Z4N7XLFG.js.map} +0 -0
@@ -27,7 +27,7 @@ import {
27
27
  useOgPlaceholderUrl,
28
28
  useOnboardingState,
29
29
  useToast
30
- } from "./chunk-MG4AMIFX.js";
30
+ } from "./chunk-5NYXGWBN.js";
31
31
  import {
32
32
  AlertCircleIcon,
33
33
  AnthropicLogoGreyIcon,
@@ -249,7 +249,7 @@ import {
249
249
  acquireClient,
250
250
  releaseClient,
251
251
  startConnectionLifecycle
252
- } from "./chunk-PHWQLKVE.js";
252
+ } from "./chunk-P47NN7RY.js";
253
253
  import {
254
254
  __esm
255
255
  } from "./chunk-GGWZFCYS.js";
@@ -14000,13 +14000,15 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
14000
14000
  import * as React40 from "react";
14001
14001
  init_cn();
14002
14002
  import { jsx as jsx96, jsxs as jsxs74 } from "react/jsx-runtime";
14003
- var CheckboxBlock = React40.forwardRef(({ id, checked, defaultChecked, onCheckedChange, label, description, truncateLabel, disabled, error, className }, ref) => /* @__PURE__ */ jsxs74("div", { className: cn("relative flex w-full flex-col", className), children: [
14003
+ var CheckboxBlock = React40.forwardRef(({ id, checked, defaultChecked, onCheckedChange, label, description, truncateLabel, trailing, disabled, error, className }, ref) => /* @__PURE__ */ jsxs74("div", { className: cn("relative flex w-full flex-col", className), children: [
14004
14004
  /* @__PURE__ */ jsxs74(
14005
14005
  "label",
14006
14006
  {
14007
14007
  htmlFor: id,
14008
14008
  className: cn(
14009
14009
  "flex items-center gap-[var(--spacing-system-s)] rounded-md ring-1 ring-inset w-full",
14010
+ // Trailing content stacks full-width below the text on mobile.
14011
+ trailing && "flex-wrap md:flex-nowrap",
14010
14012
  "p-[var(--spacing-system-sf)]",
14011
14013
  !description && "min-h-[44px] md:min-h-[48px]",
14012
14014
  description && "min-h-[60px] md:min-h-[64px]",
@@ -14054,7 +14056,8 @@ var CheckboxBlock = React40.forwardRef(({ id, checked, defaultChecked, onChecked
14054
14056
  "text-h6 !leading-4",
14055
14057
  "text-ods-text-secondary select-none break-words"
14056
14058
  ), children: description })
14057
- ] })
14059
+ ] }),
14060
+ trailing && /* @__PURE__ */ jsx96("div", { className: "flex w-full shrink-0 items-center md:ml-auto md:w-auto", children: trailing })
14058
14061
  ]
14059
14062
  }
14060
14063
  ),
@@ -31988,14 +31991,7 @@ var PolicyRow = ({ policy, categoryId, editMode, onPermissionChange }) => {
31988
31991
  /* @__PURE__ */ jsx253("div", { className: "bg-ods-bg border border-ods-border rounded-md flex items-center justify-center w-8 h-8 shrink-0", children: /* @__PURE__ */ jsx253(ToolIcon, { toolType: policy.toolName, size: 16 }) }),
31989
31992
  /* @__PURE__ */ jsxs197("div", { className: "flex-1 flex flex-col min-w-0", children: [
31990
31993
  /* @__PURE__ */ jsx253("p", { className: "text-h4 text-ods-text-primary truncate", title: policy.name, children: policy.name }),
31991
- /* @__PURE__ */ jsx253(
31992
- "p",
31993
- {
31994
- className: "text-h6 text-ods-text-secondary truncate md:whitespace-normal md:break-all",
31995
- title: policy.commandPattern,
31996
- children: policy.commandPattern
31997
- }
31998
- ),
31994
+ /* @__PURE__ */ jsx253("p", { className: "text-h6 text-ods-text-secondary break-all", children: policy.commandPattern }),
31999
31995
  editMode ? /* @__PURE__ */ jsx253("div", { className: "md:hidden mt-[var(--spacing-system-xsf)]", children: /* @__PURE__ */ jsx253(
32000
31996
  ApprovalLevelDropdown,
32001
31997
  {
@@ -46960,8 +46956,10 @@ function useChunkCatchup({
46960
46956
  }) {
46961
46957
  const processedSequenceKeys = useRef64(/* @__PURE__ */ new Set());
46962
46958
  const lastSequenceId = useRef64(null);
46959
+ const lastSequenceIdByType = useRef64(/* @__PURE__ */ new Map());
46963
46960
  const fetchingInProgress = useRef64(false);
46964
46961
  const lastFetchParams = useRef64(null);
46962
+ const pendingCatchupRef = useRef64(null);
46965
46963
  const chunkBuffer = useRef64([]);
46966
46964
  const bufferUntilInitialCatchupComplete = useRef64(false);
46967
46965
  const hasCompletedInitialCatchup = useRef64(false);
@@ -46980,8 +46978,14 @@ function useChunkCatchup({
46980
46978
  }
46981
46979
  if (chunk.sequenceId !== void 0 && chunk.sequenceId !== null) {
46982
46980
  const chunkType = typeof chunk.type === "string" ? chunk.type : "";
46983
- processedSequenceKeys.current.add(makeSeqKey(messageType, chunkType, chunk.sequenceId));
46981
+ const key = makeSeqKey(messageType, chunkType, chunk.sequenceId);
46982
+ if (processedSequenceKeys.current.has(key)) return true;
46983
+ processedSequenceKeys.current.add(key);
46984
46984
  lastSequenceId.current = chunk.sequenceId;
46985
+ const prevTypeSeq = lastSequenceIdByType.current.get(messageType);
46986
+ if (prevTypeSeq === void 0 || chunk.sequenceId > prevTypeSeq) {
46987
+ lastSequenceIdByType.current.set(messageType, chunk.sequenceId);
46988
+ }
46985
46989
  }
46986
46990
  onChunkReceivedRef.current(chunk, messageType);
46987
46991
  return true;
@@ -46991,8 +46995,8 @@ function useChunkCatchup({
46991
46995
  const buffered = [...chunkBuffer.current];
46992
46996
  chunkBuffer.current = [];
46993
46997
  buffered.sort((a, b) => {
46994
- const seqA = a.chunk.sequenceId ?? 0;
46995
- const seqB = b.chunk.sequenceId ?? 0;
46998
+ const seqA = a.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
46999
+ const seqB = b.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
46996
47000
  return seqA - seqB;
46997
47001
  });
46998
47002
  buffered.forEach(({ chunk, messageType }) => {
@@ -47007,9 +47011,14 @@ function useChunkCatchup({
47007
47011
  return;
47008
47012
  }
47009
47013
  if (hasCompletedInitialCatchup.current) {
47014
+ if (bufferUntilInitialCatchupComplete.current) {
47015
+ bufferUntilInitialCatchupComplete.current = false;
47016
+ flushBufferedRealtimeChunks();
47017
+ }
47010
47018
  return;
47011
47019
  }
47012
47020
  if (fetchingInProgress.current) {
47021
+ pendingCatchupRef.current = { dialogId: dialogId2, fromSequenceId: fromSequenceId ?? lastSequenceId.current };
47013
47022
  return;
47014
47023
  }
47015
47024
  if (lastFetchParams.current && lastFetchParams.current.dialogId === dialogId2 && lastFetchParams.current.fromSequenceId === fromSequenceId) {
@@ -47026,7 +47035,8 @@ function useChunkCatchup({
47026
47035
  try {
47027
47036
  const chunkPromises = chatTypes2.map(async (chatType) => {
47028
47037
  try {
47029
- const chunks = await fetchChunks2(dialogId2, chatType, fromSequenceId);
47038
+ const typeFromSequenceId = fromSequenceId === void 0 ? void 0 : lastSequenceIdByType.current.get(getChatTypeMessageType(chatType)) ?? null;
47039
+ const chunks = await fetchChunks2(dialogId2, chatType, typeFromSequenceId);
47030
47040
  const messageType = getChatTypeMessageType(chatType);
47031
47041
  return chunks.map((chunk) => ({ chunk, messageType }));
47032
47042
  } catch (error) {
@@ -47046,49 +47056,66 @@ function useChunkCatchup({
47046
47056
  chunkBuffer.current = [];
47047
47057
  const allChunks = [...allCatchupChunks, ...bufferedNatsChunks];
47048
47058
  allChunks.sort((a, b) => {
47049
- const seqA = a.chunk.sequenceId ?? 0;
47050
- const seqB = b.chunk.sequenceId ?? 0;
47059
+ const seqA = a.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
47060
+ const seqB = b.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
47051
47061
  return seqA - seqB;
47052
47062
  });
47053
47063
  const uniqueAllChunks = [];
47054
47064
  const seenInBatch = /* @__PURE__ */ new Set();
47055
47065
  for (const item of allChunks) {
47056
- const k = makeBatchDedupKey(item);
47057
- if (seenInBatch.has(k)) continue;
47058
- seenInBatch.add(k);
47059
- uniqueAllChunks.push(item);
47060
- }
47061
- let lastMessageStartSeqId = null;
47062
- let lastMessageEndSeqId = null;
47063
- for (let i = uniqueAllChunks.length - 1; i >= 0; i--) {
47064
- const seq = uniqueAllChunks[i].chunk.sequenceId;
47065
- if (uniqueAllChunks[i].chunk.type === MESSAGE_TYPE.MESSAGE_END && seq !== void 0 && seq !== null) {
47066
- lastMessageEndSeqId = seq;
47067
- break;
47066
+ const hasSeq = item.chunk.sequenceId !== void 0 && item.chunk.sequenceId !== null;
47067
+ if (hasSeq) {
47068
+ const k = makeBatchDedupKey(item);
47069
+ if (seenInBatch.has(k)) continue;
47070
+ seenInBatch.add(k);
47068
47071
  }
47072
+ uniqueAllChunks.push(item);
47069
47073
  }
47070
- for (let i = uniqueAllChunks.length - 1; i >= 0; i--) {
47071
- const chunk = uniqueAllChunks[i].chunk;
47072
- const seq = chunk.sequenceId;
47073
- if (chunk.type === MESSAGE_TYPE.MESSAGE_START && seq !== void 0 && seq !== null) {
47074
- if (lastMessageEndSeqId === null || seq > lastMessageEndSeqId) {
47075
- lastMessageStartSeqId = seq;
47074
+ const hasNoSeq = (item) => item.chunk.sequenceId === void 0 || item.chunk.sequenceId === null;
47075
+ const byType = /* @__PURE__ */ new Map();
47076
+ for (const item of uniqueAllChunks) {
47077
+ const list = byType.get(item.messageType);
47078
+ if (list) list.push(item);
47079
+ else byType.set(item.messageType, [item]);
47080
+ }
47081
+ const chunksToProcess = [];
47082
+ for (const items of byType.values()) {
47083
+ let lastMessageStartSeqId = null;
47084
+ let lastMessageEndSeqId = null;
47085
+ for (let i = items.length - 1; i >= 0; i--) {
47086
+ const seq = items[i].chunk.sequenceId;
47087
+ if (items[i].chunk.type === MESSAGE_TYPE.MESSAGE_END && seq !== void 0 && seq !== null) {
47088
+ lastMessageEndSeqId = seq;
47076
47089
  break;
47077
47090
  }
47078
47091
  }
47092
+ for (let i = items.length - 1; i >= 0; i--) {
47093
+ const chunk = items[i].chunk;
47094
+ const seq = chunk.sequenceId;
47095
+ if (chunk.type === MESSAGE_TYPE.MESSAGE_START && seq !== void 0 && seq !== null) {
47096
+ if (lastMessageEndSeqId === null || seq > lastMessageEndSeqId) {
47097
+ lastMessageStartSeqId = seq;
47098
+ break;
47099
+ }
47100
+ }
47101
+ }
47102
+ if (lastMessageStartSeqId !== null) {
47103
+ chunksToProcess.push(
47104
+ ...items.filter((item) => hasNoSeq(item) || item.chunk.sequenceId >= lastMessageStartSeqId)
47105
+ );
47106
+ } else if (lastMessageEndSeqId !== null) {
47107
+ chunksToProcess.push(
47108
+ ...items.filter((item) => hasNoSeq(item) || item.chunk.sequenceId > lastMessageEndSeqId)
47109
+ );
47110
+ } else {
47111
+ chunksToProcess.push(...items);
47112
+ }
47079
47113
  }
47080
- let chunksToProcess;
47081
- if (lastMessageStartSeqId !== null) {
47082
- chunksToProcess = uniqueAllChunks.filter(
47083
- (item) => item.chunk.sequenceId !== void 0 && item.chunk.sequenceId >= lastMessageStartSeqId
47084
- );
47085
- } else if (lastMessageEndSeqId !== null) {
47086
- chunksToProcess = uniqueAllChunks.filter(
47087
- (item) => item.chunk.sequenceId !== void 0 && item.chunk.sequenceId > lastMessageEndSeqId
47088
- );
47089
- } else {
47090
- chunksToProcess = uniqueAllChunks;
47091
- }
47114
+ chunksToProcess.sort((a, b) => {
47115
+ const seqA = a.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
47116
+ const seqB = b.chunk.sequenceId ?? Number.MAX_SAFE_INTEGER;
47117
+ return seqA - seqB;
47118
+ });
47092
47119
  chunksToProcess.forEach(({ chunk, messageType }) => {
47093
47120
  if (chunk.sequenceId !== void 0 && chunk.sequenceId !== null) {
47094
47121
  const chunkType = typeof chunk.type === "string" ? chunk.type : "";
@@ -47096,6 +47123,10 @@ function useChunkCatchup({
47096
47123
  if (processedSequenceKeys.current.has(key)) return;
47097
47124
  processedSequenceKeys.current.add(key);
47098
47125
  lastSequenceId.current = chunk.sequenceId;
47126
+ const prevTypeSeq = lastSequenceIdByType.current.get(messageType);
47127
+ if (prevTypeSeq === void 0 || chunk.sequenceId > prevTypeSeq) {
47128
+ lastSequenceIdByType.current.set(messageType, chunk.sequenceId);
47129
+ }
47099
47130
  }
47100
47131
  onChunkReceivedRef.current(chunk, messageType);
47101
47132
  });
@@ -47104,26 +47135,55 @@ function useChunkCatchup({
47104
47135
  } catch (error) {
47105
47136
  console.error("Error during chunk catchup:", error);
47106
47137
  } finally {
47107
- fetchingInProgress.current = false;
47108
- if (bufferUntilInitialCatchupComplete.current) {
47109
- bufferUntilInitialCatchupComplete.current = false;
47110
- hasCompletedInitialCatchup.current = true;
47111
- flushBufferedRealtimeChunks();
47138
+ if (dialogId2 !== dialogIdRef.current) {
47139
+ if (pendingCatchupRef.current?.dialogId === dialogId2) {
47140
+ pendingCatchupRef.current = null;
47141
+ }
47142
+ } else {
47143
+ fetchingInProgress.current = false;
47144
+ const pending = pendingCatchupRef.current;
47145
+ if (pending) {
47146
+ pendingCatchupRef.current = null;
47147
+ if (pending.dialogId === dialogId2) {
47148
+ hasCompletedInitialCatchup.current = false;
47149
+ lastFetchParams.current = null;
47150
+ bufferUntilInitialCatchupComplete.current = true;
47151
+ try {
47152
+ await catchUpChunksRef.current?.(pending.fromSequenceId);
47153
+ } catch (rerunError) {
47154
+ console.error("Queued catch-up re-run failed:", rerunError);
47155
+ }
47156
+ } else if (bufferUntilInitialCatchupComplete.current) {
47157
+ bufferUntilInitialCatchupComplete.current = false;
47158
+ hasCompletedInitialCatchup.current = true;
47159
+ flushBufferedRealtimeChunks();
47160
+ }
47161
+ } else if (bufferUntilInitialCatchupComplete.current) {
47162
+ bufferUntilInitialCatchupComplete.current = false;
47163
+ hasCompletedInitialCatchup.current = true;
47164
+ flushBufferedRealtimeChunks();
47165
+ }
47112
47166
  }
47113
47167
  }
47114
47168
  }, [flushBufferedRealtimeChunks]);
47169
+ const catchUpChunksRef = useRef64(null);
47170
+ catchUpChunksRef.current = catchUpChunks;
47115
47171
  const resetChunkTracking = useCallback46(() => {
47116
47172
  processedSequenceKeys.current.clear();
47117
47173
  lastSequenceId.current = null;
47174
+ lastSequenceIdByType.current.clear();
47118
47175
  fetchingInProgress.current = false;
47119
47176
  lastFetchParams.current = null;
47177
+ pendingCatchupRef.current = null;
47120
47178
  chunkBuffer.current = [];
47121
47179
  bufferUntilInitialCatchupComplete.current = false;
47122
47180
  hasCompletedInitialCatchup.current = false;
47123
47181
  }, []);
47124
47182
  const startInitialBuffering = useCallback46(() => {
47125
- chunkBuffer.current = [];
47126
- bufferUntilInitialCatchupComplete.current = true;
47183
+ if (!bufferUntilInitialCatchupComplete.current) {
47184
+ chunkBuffer.current = [];
47185
+ bufferUntilInitialCatchupComplete.current = true;
47186
+ }
47127
47187
  hasCompletedInitialCatchup.current = false;
47128
47188
  }, []);
47129
47189
  const isBufferingActive = useCallback46(() => bufferUntilInitialCatchupComplete.current, []);
@@ -47132,8 +47192,10 @@ function useChunkCatchup({
47132
47192
  const fromSeq = lastSequenceId.current;
47133
47193
  hasCompletedInitialCatchup.current = false;
47134
47194
  lastFetchParams.current = null;
47135
- bufferUntilInitialCatchupComplete.current = true;
47136
- chunkBuffer.current = [];
47195
+ if (!bufferUntilInitialCatchupComplete.current) {
47196
+ chunkBuffer.current = [];
47197
+ bufferUntilInitialCatchupComplete.current = true;
47198
+ }
47137
47199
  await catchUpChunks(fromSeq);
47138
47200
  }, [catchUpChunks]);
47139
47201
  return {
@@ -47333,6 +47395,10 @@ function useJetStreamDialogSubscription({
47333
47395
  }
47334
47396
  };
47335
47397
  }, [enabled, wsUrl]);
47398
+ useEffect79(() => {
47399
+ highestStreamSeqRef.current = null;
47400
+ setCurrentStreamSeq(null);
47401
+ }, [dialogId]);
47336
47402
  useEffect79(() => {
47337
47403
  if (!enabled || !dialogId || !isConnected) {
47338
47404
  if (subscriptionRef.current) {
@@ -47416,10 +47482,6 @@ function useJetStreamDialogSubscription({
47416
47482
  setIsSubscribed(false);
47417
47483
  };
47418
47484
  }, [enabled, dialogId, isConnected, streamName, topic, reconnectionCount]);
47419
- useEffect79(() => {
47420
- highestStreamSeqRef.current = null;
47421
- setCurrentStreamSeq(null);
47422
- }, [dialogId]);
47423
47485
  return { isConnected, isSubscribed, reconnectionCount, currentStreamSeq };
47424
47486
  }
47425
47487
 
@@ -47560,6 +47622,8 @@ function useNatsDialogSubscription({
47560
47622
  };
47561
47623
  }, [enabled, wsUrl]);
47562
47624
  const topicsKey = topics.join(",");
47625
+ const topicsRef = useRef66(topics);
47626
+ topicsRef.current = topics;
47563
47627
  const lastSubscribedDialogIdRef = useRef66(null);
47564
47628
  const isConnectedRef = useRef66(isConnected);
47565
47629
  useEffect80(() => {
@@ -47619,7 +47683,7 @@ function useNatsDialogSubscription({
47619
47683
  } catch {
47620
47684
  }
47621
47685
  };
47622
- topics.forEach((topic) => {
47686
+ topicsRef.current.forEach((topic) => {
47623
47687
  const subscription = client.subscribeBytes(
47624
47688
  `chat.${dialogId}.${topic}`,
47625
47689
  handleMessage(topic),
@@ -47647,7 +47711,7 @@ function useNatsDialogSubscription({
47647
47711
  lastSubscribedDialogIdRef.current = null;
47648
47712
  abortControllerRef.current = null;
47649
47713
  };
47650
- }, [enabled, dialogId, topicsKey, topics]);
47714
+ }, [enabled, dialogId, topicsKey]);
47651
47715
  useEffect80(() => {
47652
47716
  if (!enabled || !currentDialogIdRef.current || !isConnected) {
47653
47717
  return;
@@ -47670,7 +47734,7 @@ function useNatsDialogSubscription({
47670
47734
  } catch {
47671
47735
  }
47672
47736
  };
47673
- topics.forEach((topic) => {
47737
+ topicsRef.current.forEach((topic) => {
47674
47738
  const subscription = client.subscribeBytes(
47675
47739
  `chat.${dialogId2}.${topic}`,
47676
47740
  handleMessage(topic),
@@ -47684,7 +47748,7 @@ function useNatsDialogSubscription({
47684
47748
  } else if (subscriptionRefs.current.size > 0) {
47685
47749
  setIsSubscribed(true);
47686
47750
  }
47687
- }, [isConnected, enabled, topics, topicsKey]);
47751
+ }, [isConnected, enabled, topicsKey]);
47688
47752
  return { isConnected, isSubscribed, reconnectionCount };
47689
47753
  }
47690
47754
  function buildNatsWsUrl(apiBaseUrl, options) {
@@ -48045,6 +48109,10 @@ var MessageSegmentAccumulator = class {
48045
48109
  const hasCall = seg.data.toolCalls.some((c) => c.toolExecutionRequestId === execId);
48046
48110
  if (!hasCall) return seg;
48047
48111
  const prev = seg.data.executions?.[execId];
48112
+ if (toolData.type === "EXECUTING_TOOL" && prev?.status === "done") {
48113
+ matched = true;
48114
+ return seg;
48115
+ }
48048
48116
  const next = toolData.type === "EXECUTED_TOOL" ? { status: "done", result: toolData.result, success: toolData.success } : { status: "executing", result: prev?.result, success: prev?.success };
48049
48117
  matched = true;
48050
48118
  return {
@@ -48508,6 +48576,16 @@ function useRealtimeChunkProcessor(options) {
48508
48576
  explanation: escalatedData.explanation,
48509
48577
  approvalType: escalatedData.approvalType
48510
48578
  });
48579
+ const emitResolved = (segments) => {
48580
+ if (isInStreamRef.current) {
48581
+ emitSegments(segments);
48582
+ return;
48583
+ }
48584
+ const delta = segments.filter(
48585
+ (s) => s.type === "approval_request" && s.data.requestId === requestId || s.type === "approval_batch" && s.data.approvalRequestId === requestId
48586
+ );
48587
+ emitSegments(delta, { append: true });
48588
+ };
48511
48589
  if (escalatedData.toolCalls && escalatedData.toolCalls.length > 0) {
48512
48590
  if (batchApprovalsEnabled) {
48513
48591
  const segments = accumulator.addApprovalBatch(
@@ -48518,7 +48596,7 @@ function useRealtimeChunkProcessor(options) {
48518
48596
  void 0,
48519
48597
  resolvedByName
48520
48598
  );
48521
- emitSegments(segments);
48599
+ emitResolved(segments);
48522
48600
  } else {
48523
48601
  let segments = accumulator.getSegments();
48524
48602
  for (const call of escalatedData.toolCalls) {
@@ -48531,7 +48609,7 @@ function useRealtimeChunkProcessor(options) {
48531
48609
  status
48532
48610
  );
48533
48611
  }
48534
- emitSegments(segments);
48612
+ emitResolved(segments);
48535
48613
  }
48536
48614
  } else {
48537
48615
  const segments = accumulator.addApprovalRequest(
@@ -48541,7 +48619,7 @@ function useRealtimeChunkProcessor(options) {
48541
48619
  escalatedData.approvalType,
48542
48620
  status
48543
48621
  );
48544
- emitSegments(segments);
48622
+ emitResolved(segments);
48545
48623
  }
48546
48624
  } else {
48547
48625
  accumulator.updateApprovalStatus(requestId, status, resolvedByName);
@@ -48621,6 +48699,8 @@ function useRealtimeChunkProcessor(options) {
48621
48699
  hasInitializedWithData.current = false;
48622
48700
  sawDirectMessageRef.current = false;
48623
48701
  directTeardownFiredRef.current = false;
48702
+ isInStreamRef.current = false;
48703
+ hasEverStreamedRef.current = false;
48624
48704
  }, []);
48625
48705
  const updateApprovalStatus = useCallback47(
48626
48706
  (requestId, status, resolvedByName) => {
@@ -50151,7 +50231,15 @@ function processHistoricalMessages(messages, options = {}) {
50151
50231
  onReject,
50152
50232
  chatTypeFilter,
50153
50233
  approvalStatuses = {},
50154
- displayApprovalTypes
50234
+ // An omitted option means "display every approval type" — the original
50235
+ // history semantics. Deliberately NOT defaulted to the realtime
50236
+ // processor's ['CLIENT']: consumers that pass a wider list to their
50237
+ // realtime processor but omit it on the history path would silently lose
50238
+ // pending non-CLIENT approval cards on every reload/reconnect refetch
50239
+ // (they also ignore `escalatedApprovals`). Realtime/history parity is
50240
+ // opt-in: pass the same explicit list to both.
50241
+ displayApprovalTypes,
50242
+ batchApprovalsEnabled
50155
50243
  } = options;
50156
50244
  const processedMessages = [];
50157
50245
  const accumulator = createMessageSegmentAccumulator({ onApprove, onReject });
@@ -50216,7 +50304,13 @@ function processHistoricalMessages(messages, options = {}) {
50216
50304
  }
50217
50305
  lastAssistantId = msg.id;
50218
50306
  messageDataArray.forEach((data) => {
50219
- processMessageData(data, accumulator, approvalStatuses, { displayApprovalTypes }, escalatedApprovals);
50307
+ processMessageData(
50308
+ data,
50309
+ accumulator,
50310
+ approvalStatuses,
50311
+ { displayApprovalTypes, batchApprovalsEnabled },
50312
+ escalatedApprovals
50313
+ );
50220
50314
  });
50221
50315
  const nextMsg = messages[index + 1];
50222
50316
  const isLastMessage = index === messages.length - 1;
@@ -50440,7 +50534,7 @@ function extractErrorMessages(messages, options = {}) {
50440
50534
  return errorMessages;
50441
50535
  }
50442
50536
  function processHistoricalMessagesWithErrors(messages, options = {}) {
50443
- const { chatTypeFilter, assistantName = "Fae", assistantType = "fae", assistantAvatar, onApprove, onReject, approvalStatuses = {}, displayApprovalTypes } = options;
50537
+ const { chatTypeFilter, assistantName = "Fae", assistantType = "fae", assistantAvatar, onApprove, onReject, approvalStatuses = {}, displayApprovalTypes, batchApprovalsEnabled } = options;
50444
50538
  const processedMessages = [];
50445
50539
  const accumulator = createMessageSegmentAccumulator({ onApprove, onReject });
50446
50540
  const escalatedApprovals = /* @__PURE__ */ new Map();
@@ -50504,7 +50598,13 @@ function processHistoricalMessagesWithErrors(messages, options = {}) {
50504
50598
  }
50505
50599
  lastAssistantId = msg.id;
50506
50600
  messageDataArray.forEach((data) => {
50507
- processMessageData(data, accumulator, approvalStatuses, { displayApprovalTypes }, escalatedApprovals);
50601
+ processMessageData(
50602
+ data,
50603
+ accumulator,
50604
+ approvalStatuses,
50605
+ { displayApprovalTypes, batchApprovalsEnabled },
50606
+ escalatedApprovals
50607
+ );
50508
50608
  });
50509
50609
  const nextMsg = messages[index + 1];
50510
50610
  const isLastMessage = index === messages.length - 1;
@@ -50533,6 +50633,65 @@ function processHistoricalMessagesWithErrors(messages, options = {}) {
50533
50633
  };
50534
50634
  }
50535
50635
 
50636
+ // src/components/chat/utils/extract-incomplete-message-state.ts
50637
+ function extractIncompleteMessageState(lastMessage) {
50638
+ if (!lastMessage || lastMessage.role !== "assistant" || typeof lastMessage.content === "string") {
50639
+ return void 0;
50640
+ }
50641
+ const segments = lastMessage.content;
50642
+ const pendingApprovals = /* @__PURE__ */ new Map();
50643
+ const executingTools = /* @__PURE__ */ new Map();
50644
+ let hasIncompleteState = false;
50645
+ segments.forEach((segment) => {
50646
+ switch (segment.type) {
50647
+ case "tool_execution":
50648
+ if (segment.data.type === "EXECUTING_TOOL") {
50649
+ const toolKey = segment.data.toolExecutionRequestId || `${segment.data.integratedToolType}-${segment.data.toolFunction}`;
50650
+ executingTools.set(toolKey, {
50651
+ integratedToolType: segment.data.integratedToolType,
50652
+ toolFunction: segment.data.toolFunction,
50653
+ toolTitle: segment.data.toolTitle,
50654
+ parameters: segment.data.parameters
50655
+ });
50656
+ hasIncompleteState = true;
50657
+ }
50658
+ break;
50659
+ case "approval_request":
50660
+ if (segment.status === "pending" && segment.data.requestId) {
50661
+ pendingApprovals.set(segment.data.requestId, {
50662
+ command: segment.data.command,
50663
+ explanation: segment.data.explanation,
50664
+ approvalType: segment.data.approvalType || "CLIENT"
50665
+ });
50666
+ hasIncompleteState = true;
50667
+ }
50668
+ break;
50669
+ case "approval_batch": {
50670
+ const allDone = !!segment.data.executions && segment.data.toolCalls.every(
50671
+ (c) => segment.data.executions?.[c.toolExecutionRequestId]?.status === "done"
50672
+ );
50673
+ if (segment.status !== "rejected" && !allDone) {
50674
+ hasIncompleteState = true;
50675
+ }
50676
+ break;
50677
+ }
50678
+ case "context_compaction":
50679
+ if (segment.status === "started") {
50680
+ hasIncompleteState = true;
50681
+ }
50682
+ break;
50683
+ }
50684
+ });
50685
+ if (!hasIncompleteState) {
50686
+ return void 0;
50687
+ }
50688
+ return {
50689
+ existingSegments: segments,
50690
+ pendingApprovals: pendingApprovals.size > 0 ? pendingApprovals : void 0,
50691
+ executingTools: executingTools.size > 0 ? executingTools : void 0
50692
+ };
50693
+ }
50694
+
50536
50695
  // src/components/chat/hooks/use-nats-chat-adapter.ts
50537
50696
  function nextId(role) {
50538
50697
  return `${role}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
@@ -50555,6 +50714,110 @@ function updateTrailingAssistant(prev, segments) {
50555
50714
  { ...last, segments }
50556
50715
  ];
50557
50716
  }
50717
+ var CONTENT_DEDUP_WINDOW = 4;
50718
+ var SYSTEM_DEDUP_WINDOW = 10;
50719
+ function hasRecentMessage(prev, predicate, window2) {
50720
+ const start = Math.max(0, prev.length - window2);
50721
+ for (let i = prev.length - 1; i >= start; i--) {
50722
+ if (predicate(prev[i])) return true;
50723
+ }
50724
+ return false;
50725
+ }
50726
+ function appendToTrailingAssistant(prev, segments) {
50727
+ if (segments.length === 0) return prev;
50728
+ const last = prev[prev.length - 1];
50729
+ if (!last || last.role !== "assistant") {
50730
+ return [
50731
+ ...prev,
50732
+ { id: nextId("assistant"), role: "assistant", content: "", segments }
50733
+ ];
50734
+ }
50735
+ const merged = [...last.segments ?? []];
50736
+ for (const seg of segments) {
50737
+ const tail = merged[merged.length - 1];
50738
+ if (seg.type === "text" && tail?.type === "text") {
50739
+ merged[merged.length - 1] = { type: "text", text: tail.text + seg.text };
50740
+ } else if (seg.type === "thinking" && tail?.type === "thinking") {
50741
+ merged[merged.length - 1] = { type: "thinking", text: tail.text + seg.text };
50742
+ } else if (seg.type === "approval_batch") {
50743
+ const idx = merged.findIndex(
50744
+ (m) => m.type === "approval_batch" && m.data.approvalRequestId === seg.data.approvalRequestId
50745
+ );
50746
+ if (idx !== -1) merged[idx] = seg;
50747
+ else merged.push(seg);
50748
+ } else if (seg.type === "approval_request") {
50749
+ const idx = merged.findIndex(
50750
+ (m) => m.type === "approval_request" && m.data.requestId === seg.data.requestId && m.data.command === seg.data.command
50751
+ );
50752
+ if (idx !== -1) merged[idx] = seg;
50753
+ else merged.push(seg);
50754
+ } else {
50755
+ merged.push(seg);
50756
+ }
50757
+ }
50758
+ return [...prev.slice(0, -1), { ...last, segments: merged }];
50759
+ }
50760
+ function upsertTrailingCompaction(prev, segments) {
50761
+ const compaction = [...segments].reverse().find((s) => s.type === "context_compaction");
50762
+ if (!compaction) return prev;
50763
+ const last = prev[prev.length - 1];
50764
+ if (!last || last.role !== "assistant") {
50765
+ return [
50766
+ ...prev,
50767
+ { id: nextId("assistant"), role: "assistant", content: "", segments: [compaction] }
50768
+ ];
50769
+ }
50770
+ const existing = last.segments ?? [];
50771
+ const startedIdx = existing.map((s) => s.type === "context_compaction" && s.status === "started").lastIndexOf(true);
50772
+ const merged = startedIdx !== -1 ? existing.map((s, i) => i === startedIdx ? compaction : s) : [...existing, compaction];
50773
+ return [...prev.slice(0, -1), { ...last, segments: merged }];
50774
+ }
50775
+ function applyToolExecutionToMessages(prev, segment) {
50776
+ const toolData = segment.data;
50777
+ const execId = toolData.toolExecutionRequestId;
50778
+ for (let i = prev.length - 1; i >= 0; i--) {
50779
+ const message2 = prev[i];
50780
+ if (message2.role !== "assistant" || !message2.segments) continue;
50781
+ for (let j = message2.segments.length - 1; j >= 0; j--) {
50782
+ const seg = message2.segments[j];
50783
+ if (execId && seg.type === "approval_batch" && seg.data.toolCalls.some((c) => c.toolExecutionRequestId === execId)) {
50784
+ const prevExec = seg.data.executions?.[execId];
50785
+ if (toolData.type === "EXECUTING_TOOL" && prevExec?.status === "done") {
50786
+ return prev;
50787
+ }
50788
+ const nextExec = toolData.type === "EXECUTED_TOOL" ? { status: "done", result: toolData.result, success: toolData.success } : { status: "executing", result: prevExec?.result, success: prevExec?.success };
50789
+ const nextSegments = [...message2.segments];
50790
+ nextSegments[j] = {
50791
+ ...seg,
50792
+ data: { ...seg.data, executions: { ...seg.data.executions ?? {}, [execId]: nextExec } }
50793
+ };
50794
+ const next = [...prev];
50795
+ next[i] = { ...message2, segments: nextSegments };
50796
+ return next;
50797
+ }
50798
+ if (seg.type === "tool_execution") {
50799
+ const matches = execId ? seg.data.toolExecutionRequestId === execId : seg.data.type === "EXECUTING_TOOL" && seg.data.integratedToolType === toolData.integratedToolType && seg.data.toolFunction === toolData.toolFunction;
50800
+ if (!matches) continue;
50801
+ if (toolData.type === "EXECUTING_TOOL" && seg.data.type === "EXECUTED_TOOL") {
50802
+ return prev;
50803
+ }
50804
+ const nextSegments = [...message2.segments];
50805
+ nextSegments[j] = {
50806
+ type: "tool_execution",
50807
+ data: {
50808
+ ...toolData,
50809
+ toolTitle: toolData.toolTitle ?? seg.data.toolTitle,
50810
+ parameters: toolData.parameters || seg.data.parameters
50811
+ }
50812
+ };
50813
+ const next = [...prev];
50814
+ next[i] = { ...message2, segments: nextSegments };
50815
+ return next;
50816
+ }
50817
+ }
50818
+ }
50819
+ return appendToTrailingAssistant(prev, [segment]);
50820
+ }
50558
50821
  function mapProcessedToUnified(processed) {
50559
50822
  const out = [];
50560
50823
  for (const m of processed) {
@@ -50594,6 +50857,7 @@ function useNatsChatAdapter(config, options = {}) {
50594
50857
  fetchChunks,
50595
50858
  topics,
50596
50859
  batchApprovalsEnabled,
50860
+ displayApprovalTypes,
50597
50861
  fetchDialogs,
50598
50862
  fetchDialogMessages,
50599
50863
  createDialog: createDialogCallback,
@@ -50613,11 +50877,17 @@ function useNatsChatAdapter(config, options = {}) {
50613
50877
  const isManagedMode = fetchDialogs !== void 0;
50614
50878
  const [internalDialogId, setInternalDialogId] = useState116(null);
50615
50879
  const dialogId = isManagedMode ? internalDialogId : controlledDialogId !== void 0 ? controlledDialogId : null;
50880
+ const currentDialogIdRef = useRef74(dialogId);
50881
+ currentDialogIdRef.current = dialogId;
50616
50882
  const [messages, setMessages] = useState116([]);
50617
50883
  const [streamingPhase, setStreamingPhase] = useState116("idle");
50618
50884
  const streamingPhaseRef = useRef74(streamingPhase);
50619
50885
  streamingPhaseRef.current = streamingPhase;
50620
- const suppressAgentBusyRef = useRef74(false);
50886
+ const suppressAgentBusyRef = useRef74(0);
50887
+ const adoptTrailingAssistantRef = useRef74(false);
50888
+ const hasConnectedOnceRef = useRef74(false);
50889
+ const seenParticipantSeqsRef = useRef74(/* @__PURE__ */ new Set());
50890
+ const pendingEchoTextsRef = useRef74([]);
50621
50891
  const [approvalStatuses, setApprovalStatuses] = useState116({});
50622
50892
  const [dialogs, setDialogs] = useState116([]);
50623
50893
  const [dialogsNextCursor, setDialogsNextCursor] = useState116(null);
@@ -50683,17 +50953,39 @@ function useNatsChatAdapter(config, options = {}) {
50683
50953
  return handleRejectRef.current(requestId);
50684
50954
  }, []);
50685
50955
  const callbacksRef = useRef74({
50686
- onSegmentsUpdate: (segments) => {
50956
+ onSegmentsUpdate: (segments, meta) => {
50957
+ if (meta?.append && meta.isCompacting) {
50958
+ setMessages((prev) => upsertTrailingCompaction(prev, segments));
50959
+ return;
50960
+ }
50961
+ if (meta?.append) {
50962
+ setMessages((prev) => appendToTrailingAssistant(prev, segments));
50963
+ return;
50964
+ }
50687
50965
  setMessages((prev) => updateTrailingAssistant(prev, segments));
50688
50966
  },
50689
- onStreamStart: () => setStreamingPhase("streaming"),
50967
+ onStreamStart: () => {
50968
+ setStreamingPhase("streaming");
50969
+ const adoptTail = adoptTrailingAssistantRef.current;
50970
+ adoptTrailingAssistantRef.current = false;
50971
+ setMessages((prev) => {
50972
+ const last = prev[prev.length - 1];
50973
+ if (!last || last.role !== "assistant" || adoptTail) return prev;
50974
+ const hasContent = (last.segments?.length ?? 0) > 0 || last.content !== "";
50975
+ if (!hasContent) return prev;
50976
+ return [
50977
+ ...prev,
50978
+ { id: nextId("assistant"), role: "assistant", content: "", segments: [] }
50979
+ ];
50980
+ });
50981
+ },
50690
50982
  onStreamEnd: () => setStreamingPhase("idle"),
50691
50983
  // Agent is executing (approved) commands outside an open stream — keep
50692
50984
  // the composer locked exactly like the in-stream phases. An open stream
50693
50985
  // keeps ownership: only 'idle' upgrades to 'thinking'. Suppressed during
50694
50986
  // initial catchup so a replayed dead tail can't lock a finished dialog.
50695
50987
  onAgentBusy: () => {
50696
- if (suppressAgentBusyRef.current) return;
50988
+ if (suppressAgentBusyRef.current > 0) return;
50697
50989
  setStreamingPhase((p) => p === "idle" ? "thinking" : p);
50698
50990
  },
50699
50991
  // Terminal turn failures can arrive without a MESSAGE_END; unlock the
@@ -50708,27 +51000,171 @@ function useNatsChatAdapter(config, options = {}) {
50708
51000
  provider: meta.providerName || null,
50709
51001
  modelLabel: meta.modelDisplayName || meta.modelName || null,
50710
51002
  contextWindowMaxTokens: meta.contextWindow || null
50711
- })
51003
+ }),
51004
+ // Post-MESSAGE_END tool chunks (approved commands executing after the
51005
+ // approval bubble, async batch results). Routed through the cross-message
51006
+ // updater — the accumulator was reset at MESSAGE_END, so the old
51007
+ // fallthrough replaced the whole trailing bubble with one tool segment.
51008
+ onToolExecuted: (segment) => {
51009
+ setMessages((prev) => applyToolExecutionToMessages(prev, segment));
51010
+ },
51011
+ // Cross-message approval status flip. Wiring this also makes the
51012
+ // processor skip its cumulative re-emit for resolved approvals — which,
51013
+ // post-MESSAGE_END, was an empty array that blanked the trailing bubble.
51014
+ onApprovalResolved: (requestId, status, _approvalType, resolvedByName) => {
51015
+ setMessages((prev) => {
51016
+ let changed = false;
51017
+ const next = prev.map((m) => {
51018
+ if (m.role !== "assistant" || !m.segments) return m;
51019
+ let msgChanged = false;
51020
+ const segs = m.segments.map((s) => {
51021
+ if (s.type === "approval_request" && s.data.requestId === requestId && s.status !== status) {
51022
+ msgChanged = true;
51023
+ return { ...s, status };
51024
+ }
51025
+ if (s.type === "approval_batch" && s.data.approvalRequestId === requestId) {
51026
+ const nextResolvedBy = resolvedByName ?? s.resolvedByName;
51027
+ if (s.status === status && nextResolvedBy === s.resolvedByName) return s;
51028
+ msgChanged = true;
51029
+ return { ...s, status, resolvedByName: nextResolvedBy };
51030
+ }
51031
+ return s;
51032
+ });
51033
+ if (!msgChanged) return m;
51034
+ changed = true;
51035
+ return { ...m, segments: segs };
51036
+ });
51037
+ return changed ? next : prev;
51038
+ });
51039
+ setApprovalStatuses(
51040
+ (prev) => prev[requestId] === status ? prev : { ...prev, [requestId]: status }
51041
+ );
51042
+ },
51043
+ // MESSAGE_REQUEST echo — a user message from THIS or another session.
51044
+ onUserMessage: (text, meta) => {
51045
+ if (!text) return;
51046
+ const seq = meta?.streamSeq;
51047
+ if (typeof seq === "number") {
51048
+ if (seenParticipantSeqsRef.current.has(seq)) return;
51049
+ seenParticipantSeqsRef.current.add(seq);
51050
+ }
51051
+ if (meta?.ownerType !== "ADMIN") {
51052
+ const echoIdx = pendingEchoTextsRef.current.indexOf(text);
51053
+ if (echoIdx !== -1) {
51054
+ pendingEchoTextsRef.current.splice(echoIdx, 1);
51055
+ return;
51056
+ }
51057
+ }
51058
+ const authorType = meta?.ownerType === "ADMIN" ? "admin" : "user";
51059
+ setMessages((prev) => {
51060
+ if (typeof seq !== "number" && hasRecentMessage(
51061
+ prev,
51062
+ (m) => m.role === "user" && (m.authorType ?? "user") === authorType && m.content === text,
51063
+ CONTENT_DEDUP_WINDOW
51064
+ )) {
51065
+ return prev;
51066
+ }
51067
+ return [
51068
+ ...prev,
51069
+ {
51070
+ id: nextId("user"),
51071
+ role: "user",
51072
+ content: text,
51073
+ ...meta?.displayName ? { name: meta.displayName } : {},
51074
+ authorType,
51075
+ ...meta?.contextItems && meta.contextItems.length > 0 ? {
51076
+ contextItems: meta.contextItems.map((ci) => ({
51077
+ type: ci.type,
51078
+ id: ci.id,
51079
+ label: ci.label ?? ci.id
51080
+ }))
51081
+ } : {}
51082
+ }
51083
+ ];
51084
+ });
51085
+ },
51086
+ // Technician / admin direct message into the dialog.
51087
+ onDirectMessage: (text, meta) => {
51088
+ if (!text) return;
51089
+ const seq = meta?.streamSeq;
51090
+ if (typeof seq === "number") {
51091
+ if (seenParticipantSeqsRef.current.has(seq)) return;
51092
+ seenParticipantSeqsRef.current.add(seq);
51093
+ }
51094
+ setMessages((prev) => {
51095
+ if (typeof seq !== "number" && hasRecentMessage(
51096
+ prev,
51097
+ (m) => m.role === "user" && m.authorType === "admin" && m.content === text,
51098
+ CONTENT_DEDUP_WINDOW
51099
+ )) {
51100
+ return prev;
51101
+ }
51102
+ return [
51103
+ ...prev,
51104
+ {
51105
+ id: `direct-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
51106
+ role: "user",
51107
+ content: text,
51108
+ name: meta?.displayName ?? "Admin",
51109
+ authorType: "admin"
51110
+ }
51111
+ ];
51112
+ });
51113
+ },
51114
+ // System notice — rendered as a name-only row (same shape the history
51115
+ // processor produces via `pushStandaloneMessages`).
51116
+ onSystemMessage: (text, meta) => {
51117
+ if (!text) return;
51118
+ const seq = meta?.streamSeq;
51119
+ if (typeof seq === "number") {
51120
+ if (seenParticipantSeqsRef.current.has(seq)) return;
51121
+ seenParticipantSeqsRef.current.add(seq);
51122
+ }
51123
+ setMessages((prev) => {
51124
+ if (typeof seq !== "number" && hasRecentMessage(prev, (m) => m.authorType === "system" && m.name === text, SYSTEM_DEDUP_WINDOW)) {
51125
+ return prev;
51126
+ }
51127
+ return [
51128
+ ...prev,
51129
+ {
51130
+ id: `system-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
51131
+ role: "user",
51132
+ content: "",
51133
+ name: text,
51134
+ authorType: "system"
51135
+ }
51136
+ ];
51137
+ });
51138
+ }
50712
51139
  });
50713
51140
  const { processChunk, reset: resetAccumulator } = useRealtimeChunkProcessor({
50714
51141
  callbacks: {
50715
- onSegmentsUpdate: (segments) => callbacksRef.current.onSegmentsUpdate(segments),
51142
+ onSegmentsUpdate: (segments, meta) => callbacksRef.current.onSegmentsUpdate(segments, meta),
50716
51143
  onStreamStart: () => callbacksRef.current.onStreamStart(),
50717
51144
  onStreamEnd: () => callbacksRef.current.onStreamEnd(),
50718
51145
  onAgentBusy: () => callbacksRef.current.onAgentBusy(),
50719
51146
  onError: () => callbacksRef.current.onError(),
50720
51147
  onTokenUsage: (data) => callbacksRef.current.onTokenUsage(data),
50721
51148
  onMetadata: (meta) => callbacksRef.current.onMetadata(meta),
51149
+ onToolExecuted: (segment) => callbacksRef.current.onToolExecuted(segment),
51150
+ onApprovalResolved: (requestId, status, approvalType, resolvedByName) => callbacksRef.current.onApprovalResolved(requestId, status, approvalType, resolvedByName),
51151
+ onUserMessage: (text, meta) => callbacksRef.current.onUserMessage(text, meta),
51152
+ onDirectMessage: (text, meta) => callbacksRef.current.onDirectMessage(text, meta),
51153
+ onSystemMessage: (text, meta) => callbacksRef.current.onSystemMessage(text, meta),
50722
51154
  onApprove: accumApprove,
50723
51155
  onReject: accumReject
50724
51156
  },
50725
- batchApprovalsEnabled
51157
+ batchApprovalsEnabled,
51158
+ approvalStatuses,
51159
+ ...displayApprovalTypes ? { displayApprovalTypes } : {}
50726
51160
  });
50727
51161
  const {
50728
51162
  processChunk: catchupProcessChunk,
50729
51163
  catchUpChunks,
50730
51164
  startInitialBuffering,
50731
- resetChunkTracking
51165
+ resetChunkTracking,
51166
+ resetAndCatchUp,
51167
+ isBufferingActive
50732
51168
  } = useChunkCatchup({
50733
51169
  dialogId: active ? dialogId : null,
50734
51170
  onChunkReceived: (chunk) => processChunk(chunk),
@@ -50757,11 +51193,13 @@ function useNatsChatAdapter(config, options = {}) {
50757
51193
  onApprove: accumApprove,
50758
51194
  onReject: accumReject,
50759
51195
  approvalStatuses,
50760
- batchApprovalsEnabled
51196
+ batchApprovalsEnabled,
51197
+ ...displayApprovalTypes ? { displayApprovalTypes } : {}
50761
51198
  }
50762
51199
  );
50763
51200
  const unified = mapProcessedToUnified(rawProcessed);
50764
51201
  if (cursor === void 0) {
51202
+ adoptTrailingAssistantRef.current = isBufferingActive() && extractIncompleteMessageState(rawProcessed[rawProcessed.length - 1]) !== void 0;
50765
51203
  setMessages(unified);
50766
51204
  } else {
50767
51205
  setMessages((prev) => [...unified, ...prev]);
@@ -50785,8 +51223,10 @@ function useNatsChatAdapter(config, options = {}) {
50785
51223
  chatTypeFilter,
50786
51224
  approvalStatuses,
50787
51225
  batchApprovalsEnabled,
51226
+ displayApprovalTypes,
50788
51227
  accumApprove,
50789
- accumReject
51228
+ accumReject,
51229
+ isBufferingActive
50790
51230
  ]
50791
51231
  );
50792
51232
  const prevDialogIdRef = useRef74(void 0);
@@ -50795,6 +51235,9 @@ function useNatsChatAdapter(config, options = {}) {
50795
51235
  prevDialogIdRef.current = dialogId;
50796
51236
  historyLoadSeqRef.current += 1;
50797
51237
  resetAccumulator();
51238
+ adoptTrailingAssistantRef.current = false;
51239
+ seenParticipantSeqsRef.current.clear();
51240
+ pendingEchoTextsRef.current = [];
50798
51241
  setMessages([]);
50799
51242
  setMessagesNextCursor(null);
50800
51243
  setDialogTokenUsage(null);
@@ -50806,13 +51249,16 @@ function useNatsChatAdapter(config, options = {}) {
50806
51249
  }, [active, dialogId, loadDialogHistory, resetAccumulator]);
50807
51250
  useEffect88(() => {
50808
51251
  if (!active || !dialogId) return;
50809
- suppressAgentBusyRef.current = true;
51252
+ suppressAgentBusyRef.current += 1;
50810
51253
  resetChunkTracking();
50811
51254
  startInitialBuffering();
50812
51255
  catchUpChunks().catch((err) => {
50813
51256
  console.error("[useNatsChatAdapter] initial catchup failed:", err);
50814
51257
  }).finally(() => {
50815
- suppressAgentBusyRef.current = false;
51258
+ suppressAgentBusyRef.current = Math.max(0, suppressAgentBusyRef.current - 1);
51259
+ if (dialogId === currentDialogIdRef.current) {
51260
+ adoptTrailingAssistantRef.current = false;
51261
+ }
50816
51262
  });
50817
51263
  }, [active, dialogId, resetChunkTracking, startInitialBuffering, catchUpChunks]);
50818
51264
  useNatsDialogSubscription({
@@ -50827,6 +51273,30 @@ function useNatsChatAdapter(config, options = {}) {
50827
51273
  onEvent: (payload, messageType) => {
50828
51274
  catchupProcessChunk(payload, messageType);
50829
51275
  setConnectionState("connected");
51276
+ },
51277
+ onConnect: () => {
51278
+ if (!hasConnectedOnceRef.current) {
51279
+ hasConnectedOnceRef.current = true;
51280
+ return;
51281
+ }
51282
+ suppressAgentBusyRef.current += 1;
51283
+ startInitialBuffering();
51284
+ void (async () => {
51285
+ try {
51286
+ if (dialogId && fetchDialogMessages) {
51287
+ await loadDialogHistory(dialogId);
51288
+ }
51289
+ if (dialogId !== currentDialogIdRef.current) return;
51290
+ await resetAndCatchUp();
51291
+ } catch (err) {
51292
+ console.error("[useNatsChatAdapter] reconnect catchup failed:", err);
51293
+ } finally {
51294
+ suppressAgentBusyRef.current = Math.max(0, suppressAgentBusyRef.current - 1);
51295
+ if (dialogId === currentDialogIdRef.current) {
51296
+ adoptTrailingAssistantRef.current = false;
51297
+ }
51298
+ }
51299
+ })();
50830
51300
  }
50831
51301
  });
50832
51302
  useEffect88(() => {
@@ -50878,6 +51348,8 @@ function useNatsChatAdapter(config, options = {}) {
50878
51348
  const sendMessage = useCallback53(
50879
51349
  async (text, sendOptions) => {
50880
51350
  const hidden = sendOptions?.hidden ?? false;
51351
+ pendingEchoTextsRef.current.push(text);
51352
+ if (pendingEchoTextsRef.current.length > 5) pendingEchoTextsRef.current.shift();
50881
51353
  setMessages((prev) => [
50882
51354
  ...prev,
50883
51355
  {
@@ -51258,65 +51730,6 @@ function useUnifiedChat(options) {
51258
51730
  );
51259
51731
  }
51260
51732
 
51261
- // src/components/chat/utils/extract-incomplete-message-state.ts
51262
- function extractIncompleteMessageState(lastMessage) {
51263
- if (!lastMessage || lastMessage.role !== "assistant" || typeof lastMessage.content === "string") {
51264
- return void 0;
51265
- }
51266
- const segments = lastMessage.content;
51267
- const pendingApprovals = /* @__PURE__ */ new Map();
51268
- const executingTools = /* @__PURE__ */ new Map();
51269
- let hasIncompleteState = false;
51270
- segments.forEach((segment) => {
51271
- switch (segment.type) {
51272
- case "tool_execution":
51273
- if (segment.data.type === "EXECUTING_TOOL") {
51274
- const toolKey = segment.data.toolExecutionRequestId || `${segment.data.integratedToolType}-${segment.data.toolFunction}`;
51275
- executingTools.set(toolKey, {
51276
- integratedToolType: segment.data.integratedToolType,
51277
- toolFunction: segment.data.toolFunction,
51278
- toolTitle: segment.data.toolTitle,
51279
- parameters: segment.data.parameters
51280
- });
51281
- hasIncompleteState = true;
51282
- }
51283
- break;
51284
- case "approval_request":
51285
- if (segment.status === "pending" && segment.data.requestId) {
51286
- pendingApprovals.set(segment.data.requestId, {
51287
- command: segment.data.command,
51288
- explanation: segment.data.explanation,
51289
- approvalType: segment.data.approvalType || "CLIENT"
51290
- });
51291
- hasIncompleteState = true;
51292
- }
51293
- break;
51294
- case "approval_batch": {
51295
- const allDone = !!segment.data.executions && segment.data.toolCalls.every(
51296
- (c) => segment.data.executions?.[c.toolExecutionRequestId]?.status === "done"
51297
- );
51298
- if (segment.status !== "rejected" && !allDone) {
51299
- hasIncompleteState = true;
51300
- }
51301
- break;
51302
- }
51303
- case "context_compaction":
51304
- if (segment.status === "started") {
51305
- hasIncompleteState = true;
51306
- }
51307
- break;
51308
- }
51309
- });
51310
- if (!hasIncompleteState) {
51311
- return void 0;
51312
- }
51313
- return {
51314
- existingSegments: segments,
51315
- pendingApprovals: pendingApprovals.size > 0 ? pendingApprovals : void 0,
51316
- executingTools: executingTools.size > 0 ? executingTools : void 0
51317
- };
51318
- }
51319
-
51320
51733
  // src/components/chat/utils/history-merge.ts
51321
51734
  var SYNTHETIC_REALTIME_ID_PREFIXES = ["assistant-", "user-", "direct-", "system-", "error-"];
51322
51735
  function isSyntheticRealtimeId(id) {
@@ -53701,11 +54114,14 @@ export {
53701
54114
  processHistoricalMessages,
53702
54115
  extractErrorMessages,
53703
54116
  processHistoricalMessagesWithErrors,
54117
+ extractIncompleteMessageState,
54118
+ appendToTrailingAssistant,
54119
+ upsertTrailingCompaction,
54120
+ applyToolExecutionToMessages,
53704
54121
  mapProcessedToUnified,
53705
54122
  historicalToUnified,
53706
54123
  useNatsChatAdapter,
53707
54124
  useUnifiedChat,
53708
- extractIncompleteMessageState,
53709
54125
  SYNTHETIC_REALTIME_ID_PREFIXES,
53710
54126
  flattenMessagePagesChronological,
53711
54127
  maxPersistedStreamSeq,
@@ -54122,4 +54538,4 @@ export {
54122
54538
  LogsList,
54123
54539
  assets
54124
54540
  };
54125
- //# sourceMappingURL=chunk-NQODKJ2F.js.map
54541
+ //# sourceMappingURL=chunk-3BHV2QAH.js.map