@flamingo-stack/openframe-frontend-core 0.0.488 → 0.0.490-1506.3944.1

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 (311) hide show
  1. package/dist/chat-protocol/decode.d.ts +58 -0
  2. package/dist/chat-protocol/decode.d.ts.map +1 -0
  3. package/dist/chat-protocol/encode.d.ts +44 -0
  4. package/dist/chat-protocol/encode.d.ts.map +1 -0
  5. package/dist/chat-protocol/env-flag.d.ts +35 -0
  6. package/dist/chat-protocol/env-flag.d.ts.map +1 -0
  7. package/dist/chat-protocol/events.d.ts +196 -0
  8. package/dist/chat-protocol/events.d.ts.map +1 -0
  9. package/dist/chat-protocol/frames.d.ts +213 -0
  10. package/dist/chat-protocol/frames.d.ts.map +1 -0
  11. package/dist/chat-protocol/index.cjs +494 -0
  12. package/dist/chat-protocol/index.cjs.map +1 -0
  13. package/dist/chat-protocol/index.d.ts +18 -0
  14. package/dist/chat-protocol/index.d.ts.map +1 -0
  15. package/dist/chat-protocol/index.js +478 -0
  16. package/dist/chat-protocol/index.js.map +1 -0
  17. package/dist/chat-protocol/ip-normalize.d.ts +44 -0
  18. package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
  19. package/dist/chat-protocol/nats-decoder.d.ts +26 -0
  20. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
  21. package/dist/{chunk-AJNDDXKC.js → chunk-4NWARV5I.js} +3 -3
  22. package/dist/{chunk-GQX6LFFA.cjs → chunk-5CJR2GZ4.cjs} +42 -42
  23. package/dist/{chunk-GQX6LFFA.cjs.map → chunk-5CJR2GZ4.cjs.map} +1 -1
  24. package/dist/{chunk-EUYIFALO.cjs → chunk-5EYRXSY5.cjs} +14 -14
  25. package/dist/{chunk-EUYIFALO.cjs.map → chunk-5EYRXSY5.cjs.map} +1 -1
  26. package/dist/{chunk-5RXKOGH6.cjs → chunk-5LMGFI4J.cjs} +7011 -4777
  27. package/dist/chunk-5LMGFI4J.cjs.map +1 -0
  28. package/dist/{chunk-KFQXO7QB.js → chunk-6GT557KN.js} +1 -1
  29. package/dist/chunk-6GT557KN.js.map +1 -0
  30. package/dist/{chunk-77DFPD35.js → chunk-BE6DK7QY.js} +12267 -10033
  31. package/dist/chunk-BE6DK7QY.js.map +1 -0
  32. package/dist/{chunk-A7IPON2V.cjs → chunk-BIKOJ4FH.cjs} +5 -5
  33. package/dist/{chunk-A7IPON2V.cjs.map → chunk-BIKOJ4FH.cjs.map} +1 -1
  34. package/dist/{chunk-HQYJINFV.js → chunk-CORIBKHG.js} +4 -4
  35. package/dist/{chunk-YEEHB4T6.cjs → chunk-CSYSYJCF.cjs} +40 -40
  36. package/dist/{chunk-YEEHB4T6.cjs.map → chunk-CSYSYJCF.cjs.map} +1 -1
  37. package/dist/{chunk-SIKCXKGB.cjs → chunk-DSDZ5T4P.cjs} +7 -7
  38. package/dist/{chunk-SIKCXKGB.cjs.map → chunk-DSDZ5T4P.cjs.map} +1 -1
  39. package/dist/{chunk-PNXNWGBY.js → chunk-GTWO5NAY.js} +2 -2
  40. package/dist/{chunk-SCCTJ2UI.js → chunk-HSACATCD.js} +4 -4
  41. package/dist/{chunk-PECGZWQR.cjs → chunk-KP5KRRD6.cjs} +3 -3
  42. package/dist/{chunk-PECGZWQR.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
  43. package/dist/{chunk-2B6773QM.cjs → chunk-LILQJ3UZ.cjs} +1 -1
  44. package/dist/chunk-LILQJ3UZ.cjs.map +1 -0
  45. package/dist/{chunk-RZSA6MZL.cjs → chunk-NWX7MMF5.cjs} +15 -15
  46. package/dist/{chunk-RZSA6MZL.cjs.map → chunk-NWX7MMF5.cjs.map} +1 -1
  47. package/dist/{chunk-V3CNNMY3.cjs → chunk-P4WTZU6L.cjs} +18 -18
  48. package/dist/{chunk-V3CNNMY3.cjs.map → chunk-P4WTZU6L.cjs.map} +1 -1
  49. package/dist/{chunk-KLYHZRAD.js → chunk-PB3BTZDU.js} +2 -2
  50. package/dist/{chunk-FZOBQG6M.cjs → chunk-Q6E7VJ4W.cjs} +37 -36
  51. package/dist/chunk-Q6E7VJ4W.cjs.map +1 -0
  52. package/dist/{chunk-EYEIVYYF.cjs → chunk-QSCHCZL3.cjs} +7 -7
  53. package/dist/{chunk-EYEIVYYF.cjs.map → chunk-QSCHCZL3.cjs.map} +1 -1
  54. package/dist/{chunk-YJSKQ2ZV.js → chunk-RQLBHKXN.js} +3 -3
  55. package/dist/{chunk-CWQACI4Z.js → chunk-RW2LPL72.js} +2 -2
  56. package/dist/{chunk-DX6AOHNU.cjs → chunk-S2NHJDKQ.cjs} +77 -77
  57. package/dist/{chunk-DX6AOHNU.cjs.map → chunk-S2NHJDKQ.cjs.map} +1 -1
  58. package/dist/{chunk-AHDJMEEN.js → chunk-T2EGSZFN.js} +9 -8
  59. package/dist/chunk-T2EGSZFN.js.map +1 -0
  60. package/dist/{chunk-S52LJNMU.js → chunk-TKN5667N.js} +13 -13
  61. package/dist/chunk-TKN5667N.js.map +1 -0
  62. package/dist/{chunk-3BFX6HJM.js → chunk-UUYRJPGJ.js} +6 -6
  63. package/dist/{chunk-F6BYUO5U.js → chunk-VHG6AX5V.js} +3 -3
  64. package/dist/{chunk-FOQV6RCO.cjs → chunk-VQ523ORK.cjs} +29 -29
  65. package/dist/{chunk-FOQV6RCO.cjs.map → chunk-VQ523ORK.cjs.map} +1 -1
  66. package/dist/{chunk-PXF5J24T.js → chunk-XQ2B74SI.js} +2 -2
  67. package/dist/{chunk-H7JX2BG7.cjs → chunk-YHOIAA2E.cjs} +76 -76
  68. package/dist/chunk-YHOIAA2E.cjs.map +1 -0
  69. package/dist/{chunk-LLWBPPZ4.js → chunk-YZ2OIYQ3.js} +6 -6
  70. package/dist/components/case-studies/index.cjs +10 -10
  71. package/dist/components/case-studies/index.js +4 -4
  72. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  73. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  74. package/dist/components/chat/hooks/index.d.ts +0 -1
  75. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  76. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts +47 -0
  77. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts.map +1 -0
  78. package/dist/components/chat/hooks/use-chat.d.ts +1 -0
  79. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  80. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
  81. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  82. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -53
  83. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  84. package/dist/components/chat/index.cjs +12 -4
  85. package/dist/components/chat/index.cjs.map +1 -1
  86. package/dist/components/chat/index.d.ts +1 -0
  87. package/dist/components/chat/index.d.ts.map +1 -1
  88. package/dist/components/chat/index.js +23 -15
  89. package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
  90. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
  91. package/dist/components/chat/stream/chat-stream-reducer.d.ts +367 -0
  92. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
  93. package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
  94. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
  95. package/dist/components/chat/stream/index.d.ts +17 -0
  96. package/dist/components/chat/stream/index.d.ts.map +1 -0
  97. package/dist/components/chat/stream/message-mutations.d.ts +77 -0
  98. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
  99. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
  100. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
  101. package/dist/components/chat/types/api.types.d.ts +1 -81
  102. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  103. package/dist/components/chat/types/processing.types.d.ts +2 -90
  104. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  105. package/dist/components/chat/types/unified-chat-state.types.d.ts +5 -0
  106. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  107. package/dist/components/chat/utils/chat-conversation-storage.d.ts +34 -0
  108. package/dist/components/chat/utils/chat-conversation-storage.d.ts.map +1 -0
  109. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
  110. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  111. package/dist/components/chat/utils/index.d.ts +2 -3
  112. package/dist/components/chat/utils/index.d.ts.map +1 -1
  113. package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
  114. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  115. package/dist/components/contact/index.cjs +5 -5
  116. package/dist/components/contact/index.js +4 -4
  117. package/dist/components/docs/index.cjs +7 -7
  118. package/dist/components/docs/index.js +6 -6
  119. package/dist/components/embeds/index.cjs +5 -5
  120. package/dist/components/embeds/index.js +4 -4
  121. package/dist/components/faq/index.cjs +6 -6
  122. package/dist/components/faq/index.js +5 -5
  123. package/dist/components/features/index.cjs +4 -4
  124. package/dist/components/features/index.js +3 -3
  125. package/dist/components/help-center-pages/index.cjs +23 -23
  126. package/dist/components/help-center-pages/index.js +14 -14
  127. package/dist/components/index.cjs +182 -140
  128. package/dist/components/index.cjs.map +1 -1
  129. package/dist/components/index.js +66 -24
  130. package/dist/components/index.js.map +1 -1
  131. package/dist/components/navigation/index.cjs +4 -4
  132. package/dist/components/navigation/index.js +3 -3
  133. package/dist/components/onboarding-guides/index.cjs +8 -8
  134. package/dist/components/onboarding-guides/index.js +7 -7
  135. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  136. package/dist/components/related-content/index.cjs +6 -6
  137. package/dist/components/related-content/index.js +5 -5
  138. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  139. package/dist/components/tickets/index.cjs +7 -7
  140. package/dist/components/tickets/index.js +6 -6
  141. package/dist/components/ui/index.cjs +46 -4
  142. package/dist/components/ui/index.cjs.map +1 -1
  143. package/dist/components/ui/index.d.ts +1 -2
  144. package/dist/components/ui/index.d.ts.map +1 -1
  145. package/dist/components/ui/index.js +57 -15
  146. package/dist/components/ui/markdown/base-components.d.ts +51 -0
  147. package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
  148. package/dist/components/ui/markdown/engine.d.ts +99 -0
  149. package/dist/components/ui/markdown/engine.d.ts.map +1 -0
  150. package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
  151. package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
  152. package/dist/components/ui/markdown/index.d.ts +18 -0
  153. package/dist/components/ui/markdown/index.d.ts.map +1 -0
  154. package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
  155. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
  156. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
  157. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
  158. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
  159. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
  160. package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
  161. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
  162. package/dist/components/ui/markdown/sanitize.d.ts +153 -0
  163. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
  164. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
  165. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
  166. package/dist/components/ui/markdown/streaming.d.ts +78 -0
  167. package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
  168. package/dist/components/ui/markdown/text-size.d.ts +34 -0
  169. package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
  170. package/dist/contexts/chat-runtime-context.d.ts +8 -0
  171. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  172. package/dist/contexts/index.cjs +2 -2
  173. package/dist/contexts/index.js +1 -1
  174. package/dist/hooks/index.cjs +3 -3
  175. package/dist/hooks/index.js +2 -2
  176. package/dist/index.cjs +62 -4
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.js +73 -15
  179. package/dist/utils/index.cjs +169 -40
  180. package/dist/utils/index.cjs.map +1 -1
  181. package/dist/utils/index.d.ts +1 -0
  182. package/dist/utils/index.d.ts.map +1 -1
  183. package/dist/utils/index.js +162 -41
  184. package/dist/utils/index.js.map +1 -1
  185. package/dist/utils/markdown-fences.d.ts +42 -0
  186. package/dist/utils/markdown-fences.d.ts.map +1 -0
  187. package/dist/utils/markdown-heading-id.d.ts +85 -0
  188. package/dist/utils/markdown-heading-id.d.ts.map +1 -0
  189. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  190. package/package.json +7 -1
  191. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +307 -0
  192. package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
  193. package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
  194. package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
  195. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +228 -0
  196. package/src/chat-protocol/decode.ts +278 -0
  197. package/src/chat-protocol/encode.ts +71 -0
  198. package/src/chat-protocol/env-flag.ts +39 -0
  199. package/src/chat-protocol/events.ts +232 -0
  200. package/src/chat-protocol/frames.ts +245 -0
  201. package/src/chat-protocol/index.ts +21 -0
  202. package/src/chat-protocol/ip-normalize.ts +146 -0
  203. package/src/chat-protocol/nats-decoder.ts +252 -0
  204. package/src/components/chat/__tests__/chat-message-list.test.tsx +293 -5
  205. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
  206. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +209 -0
  207. package/src/components/chat/chat-message-enhanced.tsx +111 -17
  208. package/src/components/chat/chat-message-list.tsx +358 -109
  209. package/src/components/chat/embeddable-chat.tsx +6 -0
  210. package/src/components/chat/hooks/.index.md +30 -33
  211. package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
  212. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
  213. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  214. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
  215. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
  216. package/src/components/chat/hooks/index.ts +0 -1
  217. package/src/components/chat/hooks/use-chat-history-hydration.ts +142 -0
  218. package/src/components/chat/hooks/use-chat.ts +20 -0
  219. package/src/components/chat/hooks/use-nats-chat-adapter.ts +243 -823
  220. package/src/components/chat/hooks/use-sse-chat-adapter.ts +417 -774
  221. package/src/components/chat/index.ts +5 -0
  222. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +940 -0
  223. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +756 -0
  224. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +460 -0
  225. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +894 -0
  226. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
  227. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +98 -0
  228. package/src/components/chat/stream/chat-dialog-store.ts +536 -0
  229. package/src/components/chat/stream/chat-stream-reducer.ts +1796 -0
  230. package/src/components/chat/stream/delta-batcher.ts +159 -0
  231. package/src/components/chat/stream/index.ts +55 -0
  232. package/src/components/chat/stream/message-mutations.ts +338 -0
  233. package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
  234. package/src/components/chat/thinking-display.tsx +1 -1
  235. package/src/components/chat/types/.api.types.md +39 -49
  236. package/src/components/chat/types/.processing.types.md +29 -58
  237. package/src/components/chat/types/api.types.ts +6 -73
  238. package/src/components/chat/types/processing.types.ts +11 -52
  239. package/src/components/chat/types/unified-chat-state.types.ts +6 -0
  240. package/src/components/chat/utils/.index.md +31 -40
  241. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
  242. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
  243. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +317 -0
  244. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
  245. package/src/components/chat/utils/chat-conversation-storage.ts +83 -0
  246. package/src/components/chat/utils/extract-incomplete-message-state.ts +63 -5
  247. package/src/components/chat/utils/index.ts +5 -8
  248. package/src/components/chat/utils/process-historical-messages.ts +352 -384
  249. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
  250. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  251. package/src/components/shared/product-release/release-detail-page.tsx +19 -4
  252. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
  253. package/src/components/ui/__tests__/markdown-parity.test.tsx +2528 -0
  254. package/src/components/ui/index.ts +1 -2
  255. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +115 -0
  256. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +95 -0
  257. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +309 -0
  258. package/src/components/ui/markdown/__tests__/streaming.test.tsx +388 -0
  259. package/src/components/ui/markdown/base-components.tsx +360 -0
  260. package/src/components/ui/markdown/engine.tsx +315 -0
  261. package/src/components/ui/markdown/heading-ids.ts +239 -0
  262. package/src/components/ui/markdown/index.ts +49 -0
  263. package/src/components/ui/markdown/mermaid-diagram.tsx +291 -0
  264. package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
  265. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
  266. package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
  267. package/src/components/ui/markdown/sanitize.ts +3499 -0
  268. package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
  269. package/src/components/ui/markdown/streaming.ts +362 -0
  270. package/src/components/ui/markdown/text-size.ts +106 -0
  271. package/src/components/ui/release-changelog-section.tsx +1 -1
  272. package/src/components/ui/ticket-info-section.tsx +1 -1
  273. package/src/contexts/chat-runtime-context.tsx +8 -0
  274. package/src/utils/index.ts +1 -0
  275. package/src/utils/markdown-fences.ts +127 -0
  276. package/src/utils/markdown-heading-id.ts +348 -0
  277. package/src/utils/markdown-section-extractor.ts +41 -56
  278. package/dist/chunk-2B6773QM.cjs.map +0 -1
  279. package/dist/chunk-5RXKOGH6.cjs.map +0 -1
  280. package/dist/chunk-77DFPD35.js.map +0 -1
  281. package/dist/chunk-AHDJMEEN.js.map +0 -1
  282. package/dist/chunk-FZOBQG6M.cjs.map +0 -1
  283. package/dist/chunk-H7JX2BG7.cjs.map +0 -1
  284. package/dist/chunk-KFQXO7QB.js.map +0 -1
  285. package/dist/chunk-S52LJNMU.js.map +0 -1
  286. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
  287. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
  288. package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
  289. package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
  290. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  291. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  292. package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
  293. package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
  294. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
  295. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -479
  296. package/src/components/chat/utils/.chunk-parser.md +0 -62
  297. package/src/components/chat/utils/chunk-parser.ts +0 -256
  298. package/src/components/ui/.simple-markdown-renderer.md +0 -52
  299. package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
  300. package/src/components/ui/simple-markdown-renderer.tsx +0 -964
  301. /package/dist/{chunk-AJNDDXKC.js.map → chunk-4NWARV5I.js.map} +0 -0
  302. /package/dist/{chunk-HQYJINFV.js.map → chunk-CORIBKHG.js.map} +0 -0
  303. /package/dist/{chunk-PNXNWGBY.js.map → chunk-GTWO5NAY.js.map} +0 -0
  304. /package/dist/{chunk-SCCTJ2UI.js.map → chunk-HSACATCD.js.map} +0 -0
  305. /package/dist/{chunk-KLYHZRAD.js.map → chunk-PB3BTZDU.js.map} +0 -0
  306. /package/dist/{chunk-YJSKQ2ZV.js.map → chunk-RQLBHKXN.js.map} +0 -0
  307. /package/dist/{chunk-CWQACI4Z.js.map → chunk-RW2LPL72.js.map} +0 -0
  308. /package/dist/{chunk-3BFX6HJM.js.map → chunk-UUYRJPGJ.js.map} +0 -0
  309. /package/dist/{chunk-F6BYUO5U.js.map → chunk-VHG6AX5V.js.map} +0 -0
  310. /package/dist/{chunk-PXF5J24T.js.map → chunk-XQ2B74SI.js.map} +0 -0
  311. /package/dist/{chunk-LLWBPPZ4.js.map → chunk-YZ2OIYQ3.js.map} +0 -0
@@ -1,6 +1,28 @@
1
1
  /**
2
- * Utility for processing historical messages from GraphQL/API
3
- * into display-ready format
2
+ * Utility for processing historical messages from GraphQL/API into
3
+ * display-ready format.
4
+ *
5
+ * Phase 3 of the chat unification reduced this file to DECODE + ENVELOPE:
6
+ *
7
+ * - `decodeHistoricalMessageData` maps one persisted `MessageData` item
8
+ * into the shared `ChatStreamEvent` vocabulary (historical items share
9
+ * the MESSAGE_TYPE vocabulary with NATS chunks but are NOT full chunk
10
+ * envelopes — e.g. compaction summaries ride in `summary`, not `text`,
11
+ * and APPROVAL_RESULT rows carry `resolvedByName` directly);
12
+ * - `applyHistoryEvent` replays each decoded event into the shared
13
+ * per-turn kernel (`MessageSegmentAccumulator` — the same kernel the
14
+ * master `createChatStreamReducer` instantiates) with the
15
+ * history-specific approval semantics (display-all default, pending
16
+ * tracking + `flushPendingApprovals`, `approvalStatuses` overrides);
17
+ * - the ENVELOPE (user/assistant flush-grouping, OWNER_TYPE author /
18
+ * display-name / avatar resolution, standalone SYSTEM handling,
19
+ * per-message contextItems mapping, per-turn streamSeq MAX) lives ONCE
20
+ * in `processHistory` — the previous near-duplicate second copy was
21
+ * deleted (`processHistoricalMessagesWithErrors` is an alias; the two
22
+ * snapshots were byte-identical).
23
+ *
24
+ * Adapters then feed the processed rows into the reducer via
25
+ * `initializeWithState` (see `useNatsChatAdapter.loadDialogHistory`).
4
26
  */
5
27
 
6
28
  import {
@@ -15,6 +37,7 @@ import {
15
37
  type MessageData,
16
38
  type MessageOwner,
17
39
  } from '../types'
40
+ import type { ChatStreamEvent } from '../../../chat-protocol/events'
18
41
  import { MessageSegmentAccumulator, createMessageSegmentAccumulator } from './message-segment-accumulator'
19
42
  import { getCommandText } from './tool-call-helpers'
20
43
 
@@ -53,249 +76,61 @@ function pushStandaloneMessages(
53
76
  })
54
77
  }
55
78
 
56
- /**
57
- * Result type for historical message processing
58
- */
59
- export interface ProcessHistoricalMessagesResult {
60
- messages: ProcessedMessage[]
61
- escalatedApprovals: Map<
62
- string,
63
- { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
64
- >
65
- }
79
+ // =============================================================================
80
+ // Decode persisted MessageData item shared ChatStreamEvent vocabulary
81
+ // =============================================================================
66
82
 
67
83
  /**
68
- * Process an array of historical messages into display-ready format
84
+ * Map one persisted `MessageData` item to a normalized `ChatStreamEvent`.
85
+ * Thin shim over the NATS chunk decoder's core mapping — the differences
86
+ * are exactly the persisted-row divergences from the realtime envelope:
87
+ *
88
+ * - compaction summaries arrive in `summary` (realtime: `text`);
89
+ * - APPROVAL_RESULT rows carry `resolvedByName` (realtime chunks carry
90
+ * the resolver's name as `displayName`);
91
+ * - `approvalType` defaults to 'CLIENT' (legacy history parity;
92
+ * realtime defaults APPROVAL_REQUEST to 'USER');
93
+ * - batch `toolCalls` pass through verbatim (already the persisted
94
+ * `PendingToolCallData` shape — no normalization pass).
95
+ *
96
+ * Returns `null` for rows the assistant-turn path doesn't decode (SYSTEM
97
+ * is handled by the envelope's standalone path; user TEXT rows are handled
98
+ * by the envelope's user path).
69
99
  */
70
- export function processHistoricalMessages(
71
- messages: HistoricalMessage[],
72
- options: MessageProcessingOptions = {}
73
- ): ProcessHistoricalMessagesResult {
74
- const {
75
- assistantName = 'Fae',
76
- assistantType = 'fae',
77
- assistantAvatar,
78
- onApprove,
79
- onReject,
80
- chatTypeFilter,
81
- approvalStatuses = {},
82
- // An omitted option means "display every approval type" — the original
83
- // history semantics. Deliberately NOT defaulted to the realtime
84
- // processor's ['CLIENT']: consumers that pass a wider list to their
85
- // realtime processor but omit it on the history path would silently lose
86
- // pending non-CLIENT approval cards on every reload/reconnect refetch
87
- // (they also ignore `escalatedApprovals`). Realtime/history parity is
88
- // opt-in: pass the same explicit list to both.
89
- displayApprovalTypes,
90
- batchApprovalsEnabled,
91
- } = options
92
-
93
- const processedMessages: ProcessedMessage[] = []
94
- const accumulator = createMessageSegmentAccumulator({ onApprove, onReject })
95
- const escalatedApprovals = new Map<
96
- string,
97
- { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
98
- >()
99
-
100
- let currentAssistantId: string | null = null
101
- let currentAssistantTimestamp: Date | null = null
102
- let lastAssistantId: string | null = null
103
- // MAX persisted seq across the rows grouped into the current assistant turn
104
- // — carried onto the flushed message's streamSeq for per-role merge coverage.
105
- let currentAssistantStreamSeq: number | undefined
106
-
107
- /**
108
- * Flush the current assistant message to processedMessages.
109
- * Uses the LAST message ID in the group for stable React keys across page boundaries.
110
- */
111
- const flushAssistantMessage = () => {
112
- const idToUse = lastAssistantId || currentAssistantId
113
- if (idToUse && accumulator.hasContent()) {
114
- processedMessages.push({
115
- id: idToUse,
116
- role: 'assistant',
117
- content: accumulator.getSegments(),
118
- name: assistantName,
119
- assistantType,
120
- authorType: assistantType as AuthorType,
121
- timestamp: currentAssistantTimestamp || new Date(),
122
- avatar: assistantAvatar,
123
- ...(currentAssistantStreamSeq !== undefined ? { streamSeq: currentAssistantStreamSeq } : {}),
124
- })
125
- accumulator.resetSegments()
126
- }
127
- // Reset grouping identity + seq UNCONDITIONALLY — even on an EMPTY flush (an
128
- // assistant turn whose only data was a filtered/escalated approval renders
129
- // nothing, so `hasContent()` is false and nothing is pushed). Left inside
130
- // the push-block, a stale id/timestamp and (worse) a stale
131
- // `currentAssistantStreamSeq` bleed into the NEXT assistant turn: `!currentAssistantId`
132
- // stays false so it keeps the old id/timestamp, and `Math.max` inflates its
133
- // streamSeq — which then over-covers synthetics in the history merge.
134
- currentAssistantId = null
135
- currentAssistantTimestamp = null
136
- lastAssistantId = null
137
- currentAssistantStreamSeq = undefined
138
- }
139
-
140
- messages.forEach((msg, index) => {
141
- // Filter by chat type if specified
142
- if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
143
-
144
- const messageDataArray = Array.isArray(msg.messageData)
145
- ? msg.messageData
146
- : msg.messageData
147
- ? [msg.messageData]
148
- : []
149
-
150
- const hasStandaloneData = messageDataArray.some((data) =>
151
- data.type === MESSAGE_TYPE.SYSTEM
152
- )
153
- if (hasStandaloneData) {
154
- flushAssistantMessage()
155
- pushStandaloneMessages(processedMessages, msg, messageDataArray)
156
- return
157
- }
158
-
159
- const isUserMessage =
160
- msg.owner?.type === OWNER_TYPE.CLIENT || msg.owner?.type === OWNER_TYPE.ADMIN
161
-
162
- if (isUserMessage) {
163
- flushAssistantMessage()
164
-
165
- const userAuthorType: AuthorType = msg.owner?.type === OWNER_TYPE.ADMIN ? 'admin' : 'user'
166
- messageDataArray.forEach((data) => {
167
- if (data.type === MESSAGE_TYPE.TEXT && 'text' in data && data.text) {
168
- // `TextData.contextItems` (server: `[{ type, id }]`) — the entity
169
- // context the user attached to this message. Surface it so the bubble
170
- // renders its chip strip from history (no label on the wire → fall
171
- // back to the id, matching the realtime path).
172
- const rawContext = (data as { contextItems?: Array<{ type?: unknown; id?: unknown }> }).contextItems
173
- const contextItems = Array.isArray(rawContext)
174
- ? rawContext
175
- .filter((c) => typeof c?.type === 'string' && typeof c?.id === 'string')
176
- .map((c) => ({ type: c.type as string, id: c.id as string, label: c.id as string }))
177
- : undefined
178
- processedMessages.push({
179
- id: msg.id,
180
- role: 'user',
181
- content: data.text,
182
- name: getOwnerDisplayName(msg.owner),
183
- avatar: getOwnerAvatar(msg.owner),
184
- authorType: userAuthorType,
185
- timestamp: new Date(msg.createdAt),
186
- ...(contextItems && contextItems.length > 0 ? { contextItems } : {}),
187
- ...(typeof msg.lastChunkStreamSeq === 'number' ? { streamSeq: msg.lastChunkStreamSeq } : {}),
188
- })
189
- }
190
- })
191
- } else {
192
- if (!currentAssistantId) {
193
- currentAssistantId = msg.id
194
- currentAssistantTimestamp = new Date(msg.createdAt)
195
- }
196
- lastAssistantId = msg.id
197
- if (typeof msg.lastChunkStreamSeq === 'number') {
198
- currentAssistantStreamSeq =
199
- currentAssistantStreamSeq === undefined
200
- ? msg.lastChunkStreamSeq
201
- : Math.max(currentAssistantStreamSeq, msg.lastChunkStreamSeq)
202
- }
203
-
204
- messageDataArray.forEach((data) => {
205
- processMessageData(
206
- data,
207
- accumulator,
208
- approvalStatuses,
209
- { displayApprovalTypes, batchApprovalsEnabled },
210
- escalatedApprovals,
211
- )
212
- })
213
-
214
- // Check if we should flush (next message is from user or last message)
215
- const nextMsg = messages[index + 1]
216
- const isLastMessage = index === messages.length - 1
217
- const nextIsFromUser =
218
- nextMsg &&
219
- (nextMsg.owner?.type === OWNER_TYPE.CLIENT || nextMsg.owner?.type === OWNER_TYPE.ADMIN)
220
-
221
- if (isLastMessage || nextIsFromUser) {
222
- flushAssistantMessage()
223
- }
224
- }
225
- })
226
-
227
- flushAssistantMessage()
228
-
229
- const pendingApprovalSegments = accumulator.flushPendingApprovals()
230
- if (pendingApprovalSegments.length > 0) {
231
- processedMessages.push({
232
- id: `pending-approvals-${Date.now()}`,
233
- role: 'assistant',
234
- content: pendingApprovalSegments,
235
- name: assistantName,
236
- assistantType,
237
- timestamp: new Date(),
238
- avatar: assistantAvatar,
239
- })
240
- }
241
-
242
- return {
243
- messages: processedMessages,
244
- escalatedApprovals: escalatedApprovals
245
- }
246
- }
247
-
248
- /**
249
- * Process a single message data item into segments
250
- */
251
- function processMessageData(
252
- data: MessageData,
253
- accumulator: MessageSegmentAccumulator,
254
- approvalStatuses: Record<string, string>,
255
- options: MessageProcessingOptions = {},
256
- escalatedApprovals?: Map<
257
- string,
258
- { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
259
- >
260
- ): void {
261
- // batchApprovalsEnabled is owned by the consumer (oss-tenant chat client /
262
- // openframe-frontend tickets). Defaults to ON so consumers that haven't
263
- // wired the flag yet get the batch UI; pass `false` to force legacy.
264
- const { displayApprovalTypes, batchApprovalsEnabled = true } = options
100
+ export function decodeHistoricalMessageData(data: MessageData): ChatStreamEvent | null {
265
101
  switch (data.type) {
266
102
  case MESSAGE_TYPE.TEXT:
267
103
  if ('text' in data && data.text) {
268
- accumulator.appendText(data.text)
104
+ return { type: 'text-delta', text: data.text }
269
105
  }
270
- break
106
+ return null
271
107
 
272
108
  case MESSAGE_TYPE.THINKING:
273
109
  if ('text' in data && data.text) {
274
- accumulator.appendThinking(data.text)
110
+ return { type: 'thinking-delta', text: data.text }
275
111
  }
276
- break
112
+ return null
277
113
 
278
114
  case MESSAGE_TYPE.EXECUTING_TOOL:
279
115
  if ('integratedToolType' in data) {
280
- accumulator.addToolExecution({
281
- type: 'tool_execution',
116
+ return {
117
+ type: 'tool-execution',
282
118
  data: {
283
119
  type: 'EXECUTING_TOOL',
284
120
  integratedToolType: data.integratedToolType || '',
285
121
  toolFunction: data.toolFunction || '',
286
122
  toolTitle: typeof data.title === 'string' ? data.title : undefined,
287
- toolExplanation: typeof data.toolExplanation === 'string' ? data.toolExplanation : undefined,
288
123
  parameters: data.parameters,
289
124
  toolExecutionRequestId: data.toolExecutionRequestId,
290
125
  },
291
- })
126
+ }
292
127
  }
293
- break
128
+ return null
294
129
 
295
130
  case MESSAGE_TYPE.EXECUTED_TOOL:
296
131
  if ('integratedToolType' in data) {
297
- accumulator.addToolExecution({
298
- type: 'tool_execution',
132
+ return {
133
+ type: 'tool-execution',
299
134
  data: {
300
135
  type: 'EXECUTED_TOOL',
301
136
  integratedToolType: data.integratedToolType || '',
@@ -306,218 +141,294 @@ function processMessageData(
306
141
  success: data.success,
307
142
  toolExecutionRequestId: data.toolExecutionRequestId,
308
143
  },
309
- })
144
+ }
310
145
  }
311
- break
146
+ return null
312
147
 
313
148
  case MESSAGE_TYPE.APPROVAL_REQUEST:
314
149
  if ('approvalRequestId' in data && data.approvalRequestId) {
315
- const approvalType = data.approvalType || 'CLIENT'
316
- const toolCalls: PendingToolCallData[] | undefined = Array.isArray(data.toolCalls)
317
- ? data.toolCalls
318
- : undefined
319
- const isBatch = !!toolCalls && toolCalls.length > 0
320
-
321
- if (!displayApprovalTypes || displayApprovalTypes.includes(approvalType)) {
322
- if (isBatch) {
323
- const status = (approvalStatuses[data.approvalRequestId] as ChatApprovalStatus) || 'pending'
324
- if (batchApprovalsEnabled) {
325
- accumulator.addApprovalBatch(data.approvalRequestId, approvalType, toolCalls!, status)
326
- } else {
327
- // Flag OFF — unfold batch into N legacy approval cards (same id).
328
- for (const call of toolCalls!) {
329
- if (!call.requiresApproval) continue
330
- accumulator.addApprovalRequest(
331
- data.approvalRequestId,
332
- getCommandText(call),
333
- call.toolExplanation,
334
- approvalType,
335
- status,
336
- )
337
- }
338
- }
339
- } else {
340
- // The resolution may already be known to the consumer (realtime
341
- // flipped it and fed it back via `approvalStatuses`) while the
342
- // matching APPROVAL_RESULT row is absent from the fetched history
343
- // pages. Without this, the single-approval path tracks it as
344
- // pending and `flushPendingApprovals()` resurrects it as a stale
345
- // sticky card on every history re-process. Mirror the batch path
346
- // and honor `approvalStatuses`.
347
- const resolvedStatus = approvalStatuses[data.approvalRequestId] as ChatApprovalStatus | undefined
348
- if (resolvedStatus === 'approved' || resolvedStatus === 'rejected') {
349
- accumulator.addApprovalRequest(
350
- data.approvalRequestId,
351
- data.command || '',
352
- data.explanation,
353
- approvalType,
354
- resolvedStatus,
355
- )
356
- } else {
357
- accumulator.trackApprovalRequest(data.approvalRequestId, {
358
- command: data.command || '',
359
- explanation: data.explanation,
360
- approvalType,
361
- })
362
- }
363
- }
364
- } else {
365
- escalatedApprovals?.set(data.approvalRequestId, {
366
- command: data.command || '',
367
- explanation: data.explanation,
368
- approvalType,
369
- ...(isBatch ? { toolCalls } : {}),
370
- })
150
+ return {
151
+ type: 'approval-request',
152
+ requestId: data.approvalRequestId,
153
+ approvalType: data.approvalType || 'CLIENT',
154
+ command: data.command || '',
155
+ explanation: data.explanation,
156
+ ...(Array.isArray(data.toolCalls) ? { toolCalls: data.toolCalls } : {}),
371
157
  }
372
158
  }
373
- break
159
+ return null
374
160
 
375
161
  case MESSAGE_TYPE.APPROVAL_RESULT:
376
162
  if ('approvalRequestId' in data && data.approvalRequestId) {
377
- const existingStatus = approvalStatuses[data.approvalRequestId] as ChatApprovalStatus | undefined
378
- const status: ChatApprovalStatus = existingStatus || (data.approved ? 'approved' : 'rejected')
379
- const resolvedByName = 'resolvedByName' in data ? data.resolvedByName : undefined
380
- const escalatedData = escalatedApprovals?.get(data.approvalRequestId)
163
+ return {
164
+ type: 'approval-resolved',
165
+ requestId: data.approvalRequestId,
166
+ status: data.approved ? 'approved' : 'rejected',
167
+ approvalType: data.approvalType,
168
+ resolvedByName: 'resolvedByName' in data ? data.resolvedByName : undefined,
169
+ }
170
+ }
171
+ return null
172
+
173
+ case MESSAGE_TYPE.ERROR:
174
+ if ('error' in data) {
175
+ return {
176
+ type: 'error',
177
+ title: data.error || 'An error occurred',
178
+ details: 'details' in data ? data.details : undefined,
179
+ }
180
+ }
181
+ return null
182
+
183
+ case MESSAGE_TYPE.CONTEXT_COMPACTION_START:
184
+ return { type: 'compaction', phase: 'start' }
185
+
186
+ case MESSAGE_TYPE.CONTEXT_COMPACTION_END:
187
+ return {
188
+ type: 'compaction',
189
+ phase: 'end',
190
+ summary: 'summary' in data && typeof data.summary === 'string' ? data.summary : undefined,
191
+ }
192
+
193
+ case MESSAGE_TYPE.SYSTEM:
194
+ if ('text' in data && data.text) {
195
+ return { type: 'participant', kind: 'system', text: data.text }
196
+ }
197
+ return null
198
+
199
+ default:
200
+ // Unknown message type — ignore.
201
+ return null
202
+ }
203
+ }
204
+
205
+ // =============================================================================
206
+ // Replay — decoded event → per-turn kernel (history approval semantics)
207
+ // =============================================================================
208
+
209
+ type EscalatedApprovals = Map<
210
+ string,
211
+ { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
212
+ >
213
+
214
+ /**
215
+ * Replay one decoded event into the shared per-turn segment kernel with the
216
+ * HISTORY approval semantics (an omitted `displayApprovalTypes` means
217
+ * "display every approval type" — the original history behavior; the
218
+ * realtime reducer defaults to `['CLIENT']`).
219
+ */
220
+ function applyHistoryEvent(
221
+ event: ChatStreamEvent,
222
+ accumulator: MessageSegmentAccumulator,
223
+ approvalStatuses: Record<string, string>,
224
+ options: MessageProcessingOptions,
225
+ escalatedApprovals?: EscalatedApprovals,
226
+ ): void {
227
+ // batchApprovalsEnabled is owned by the consumer (oss-tenant chat client /
228
+ // openframe-frontend tickets). Defaults to ON so consumers that haven't
229
+ // wired the flag yet get the batch UI; pass `false` to force legacy.
230
+ const { displayApprovalTypes, batchApprovalsEnabled = true } = options
231
+
232
+ switch (event.type) {
233
+ case 'text-delta':
234
+ accumulator.appendText(event.text)
235
+ break
236
+
237
+ case 'thinking-delta':
238
+ accumulator.appendThinking(event.text)
239
+ break
381
240
 
382
- if (escalatedData?.toolCalls && escalatedData.toolCalls.length > 0) {
241
+ case 'tool-execution':
242
+ accumulator.addToolExecution({ type: 'tool_execution', data: event.data })
243
+ break
244
+
245
+ case 'approval-request': {
246
+ const approvalType = event.approvalType || 'CLIENT'
247
+ const toolCalls = event.toolCalls as PendingToolCallData[] | undefined
248
+ const isBatch = !!toolCalls && toolCalls.length > 0
249
+
250
+ if (!displayApprovalTypes || displayApprovalTypes.includes(approvalType)) {
251
+ if (isBatch) {
252
+ const status = (approvalStatuses[event.requestId] as ChatApprovalStatus) || 'pending'
383
253
  if (batchApprovalsEnabled) {
384
- accumulator.addApprovalBatch(
385
- data.approvalRequestId,
386
- escalatedData.approvalType,
387
- escalatedData.toolCalls,
388
- status,
389
- undefined,
390
- resolvedByName,
391
- )
254
+ accumulator.addApprovalBatch(event.requestId, approvalType, toolCalls!, status)
392
255
  } else {
393
- for (const call of escalatedData.toolCalls) {
256
+ // Flag OFF unfold batch into N legacy approval cards (same id).
257
+ for (const call of toolCalls!) {
394
258
  if (!call.requiresApproval) continue
395
259
  accumulator.addApprovalRequest(
396
- data.approvalRequestId,
260
+ event.requestId,
397
261
  getCommandText(call),
398
262
  call.toolExplanation,
399
- escalatedData.approvalType,
263
+ approvalType,
400
264
  status,
401
265
  )
402
266
  }
403
267
  }
404
- escalatedApprovals?.delete(data.approvalRequestId)
405
- break
406
- }
407
-
408
- if (escalatedData) {
409
- accumulator.trackApprovalRequest(data.approvalRequestId, {
410
- command: escalatedData.command,
411
- explanation: escalatedData.explanation,
412
- approvalType: escalatedData.approvalType,
413
- })
414
- escalatedApprovals?.delete(data.approvalRequestId)
268
+ } else {
269
+ // The resolution may already be known to the consumer (realtime
270
+ // flipped it and fed it back via `approvalStatuses`) while the
271
+ // matching APPROVAL_RESULT row is absent from the fetched history
272
+ // pages. Without this, the single-approval path tracks it as
273
+ // pending and `flushPendingApprovals()` resurrects it as a stale
274
+ // sticky card on every history re-process. Mirror the batch path
275
+ // and honor `approvalStatuses`.
276
+ const resolvedStatus = approvalStatuses[event.requestId] as ChatApprovalStatus | undefined
277
+ if (resolvedStatus === 'approved' || resolvedStatus === 'rejected') {
278
+ accumulator.addApprovalRequest(
279
+ event.requestId,
280
+ event.command || '',
281
+ event.explanation,
282
+ approvalType,
283
+ resolvedStatus,
284
+ )
285
+ } else {
286
+ accumulator.trackApprovalRequest(event.requestId, {
287
+ command: event.command || '',
288
+ explanation: event.explanation,
289
+ approvalType,
290
+ })
291
+ }
415
292
  }
416
-
417
- // If a segment with this id is already present (batch or legacy), just flip its status.
418
- // updateApprovalStatus matches both `approval_batch` and `approval_request` segments.
419
- const before = accumulator.getSegments()
420
- const after = accumulator.updateApprovalStatus(data.approvalRequestId, status, resolvedByName)
421
- const updatedExisting = before.some((s, i) => after[i] !== s)
422
- if (updatedExisting) break
423
-
424
- accumulator.processApprovalResult(
425
- data.approvalRequestId,
426
- status === 'approved',
427
- data.approvalType || 'USER'
428
- )
293
+ } else {
294
+ escalatedApprovals?.set(event.requestId, {
295
+ command: event.command || '',
296
+ explanation: event.explanation,
297
+ approvalType,
298
+ ...(isBatch ? { toolCalls } : {}),
299
+ })
429
300
  }
430
301
  break
302
+ }
431
303
 
432
- case MESSAGE_TYPE.ERROR:
433
- if ('error' in data) {
434
- let message: string | undefined
435
- if ('details' in data && data?.details) {
436
- try {
437
- message = JSON.parse(data.details)?.error?.message
438
- } catch {
439
- message = data.details
304
+ case 'approval-resolved': {
305
+ const requestId = event.requestId
306
+ if (!requestId) break
307
+ const existingStatus = approvalStatuses[requestId] as ChatApprovalStatus | undefined
308
+ const status: ChatApprovalStatus = existingStatus || event.status
309
+ const resolvedByName = event.resolvedByName
310
+ const escalatedData = escalatedApprovals?.get(requestId)
311
+
312
+ if (escalatedData?.toolCalls && escalatedData.toolCalls.length > 0) {
313
+ if (batchApprovalsEnabled) {
314
+ accumulator.addApprovalBatch(
315
+ requestId,
316
+ escalatedData.approvalType,
317
+ escalatedData.toolCalls,
318
+ status,
319
+ undefined,
320
+ resolvedByName,
321
+ )
322
+ } else {
323
+ for (const call of escalatedData.toolCalls) {
324
+ if (!call.requiresApproval) continue
325
+ accumulator.addApprovalRequest(
326
+ requestId,
327
+ getCommandText(call),
328
+ call.toolExplanation,
329
+ escalatedData.approvalType,
330
+ status,
331
+ )
440
332
  }
441
333
  }
442
- accumulator.addError(
443
- data.error || 'An error occurred',
444
- message
445
- )
334
+ escalatedApprovals?.delete(requestId)
335
+ break
446
336
  }
447
- break
448
337
 
449
- case MESSAGE_TYPE.CONTEXT_COMPACTION_START:
450
- accumulator.addContextCompaction()
338
+ if (escalatedData) {
339
+ accumulator.trackApprovalRequest(requestId, {
340
+ command: escalatedData.command,
341
+ explanation: escalatedData.explanation,
342
+ approvalType: escalatedData.approvalType,
343
+ })
344
+ escalatedApprovals?.delete(requestId)
345
+ }
346
+
347
+ // If a segment with this id is already present (batch or legacy), just
348
+ // flip its status. updateApprovalStatus matches both `approval_batch`
349
+ // and `approval_request` segments.
350
+ const before = accumulator.getSegments()
351
+ const after = accumulator.updateApprovalStatus(requestId, status, resolvedByName)
352
+ const updatedExisting = before.some((s, i) => after[i] !== s)
353
+ if (updatedExisting) break
354
+
355
+ accumulator.processApprovalResult(
356
+ requestId,
357
+ status === 'approved',
358
+ event.approvalType || 'USER',
359
+ )
451
360
  break
361
+ }
452
362
 
453
- case MESSAGE_TYPE.CONTEXT_COMPACTION_END: {
454
- const summary = 'summary' in data && typeof data.summary === 'string' ? data.summary : undefined
455
- accumulator.completeContextCompaction(summary)
363
+ case 'error': {
364
+ let message: string | undefined
365
+ if (event.details) {
366
+ try {
367
+ message = JSON.parse(event.details)?.error?.message
368
+ } catch {
369
+ message = event.details
370
+ }
371
+ }
372
+ accumulator.addError(event.title, message)
456
373
  break
457
374
  }
458
375
 
376
+ case 'compaction':
377
+ if (event.phase === 'start') {
378
+ accumulator.addContextCompaction()
379
+ } else {
380
+ accumulator.completeContextCompaction(event.summary)
381
+ }
382
+ break
383
+
459
384
  default:
460
- // Unknown message type - ignore
385
+ // Participant/system rows are envelope concerns; everything else is
386
+ // realtime-only vocabulary that never appears in persisted rows.
461
387
  break
462
388
  }
463
389
  }
464
390
 
391
+ // =============================================================================
392
+ // Envelope — ONE implementation (previous duplicate deleted)
393
+ // =============================================================================
394
+
465
395
  /**
466
- * Extract error messages from historical messages
467
- * Returns a separate array of error messages that should be displayed
396
+ * Result type for historical message processing
468
397
  */
469
- export function extractErrorMessages(
470
- messages: HistoricalMessage[],
471
- options: MessageProcessingOptions = {}
472
- ): ProcessedMessage[] {
473
- const { assistantName = 'Fae', assistantType = 'fae', assistantAvatar, chatTypeFilter } = options
474
-
475
- const errorMessages: ProcessedMessage[] = []
476
-
477
- messages.forEach((msg) => {
478
- if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
479
-
480
- const messageDataArray = Array.isArray(msg.messageData)
481
- ? msg.messageData
482
- : msg.messageData
483
- ? [msg.messageData]
484
- : []
485
-
486
- messageDataArray.forEach((data) => {
487
- if (data.type === MESSAGE_TYPE.ERROR) {
488
- errorMessages.push({
489
- id: `${msg.id}-error`,
490
- role: 'error',
491
- content: 'error' in data && data.error ? data.error : 'An error occurred',
492
- name: assistantName,
493
- assistantType,
494
- timestamp: new Date(msg.createdAt),
495
- avatar: assistantAvatar,
496
- })
497
- }
498
- })
499
- })
500
-
501
- return errorMessages
398
+ export interface ProcessHistoricalMessagesResult {
399
+ messages: ProcessedMessage[]
400
+ escalatedApprovals: EscalatedApprovals
502
401
  }
503
402
 
504
403
  /**
505
- * Process messages and include error messages in the correct order
404
+ * Process an array of historical messages into display-ready format
506
405
  */
507
- export function processHistoricalMessagesWithErrors(
406
+ export function processHistoricalMessages(
508
407
  messages: HistoricalMessage[],
509
- options: MessageProcessingOptions = {}
408
+ options: MessageProcessingOptions = {},
510
409
  ): ProcessHistoricalMessagesResult {
511
- // displayApprovalTypes omitted = display every approval type (original
512
- // history semantics — see the matching note in processHistoricalMessages).
513
- const { chatTypeFilter, assistantName = 'Fae', assistantType = 'fae', assistantAvatar, onApprove, onReject, approvalStatuses = {}, displayApprovalTypes, batchApprovalsEnabled } = options
410
+ const {
411
+ assistantName = 'Fae',
412
+ assistantType = 'fae',
413
+ assistantAvatar,
414
+ onApprove,
415
+ onReject,
416
+ chatTypeFilter,
417
+ approvalStatuses = {},
418
+ // An omitted option means "display every approval type" — the original
419
+ // history semantics. Deliberately NOT defaulted to the realtime
420
+ // reducer's ['CLIENT']: consumers that pass a wider list to their
421
+ // realtime path but omit it on the history path would silently lose
422
+ // pending non-CLIENT approval cards on every reload/reconnect refetch
423
+ // (they also ignore `escalatedApprovals`). Realtime/history parity is
424
+ // opt-in: pass the same explicit list to both.
425
+ displayApprovalTypes,
426
+ batchApprovalsEnabled,
427
+ } = options
514
428
 
515
429
  const processedMessages: ProcessedMessage[] = []
516
430
  const accumulator = createMessageSegmentAccumulator({ onApprove, onReject })
517
- const escalatedApprovals = new Map<
518
- string,
519
- { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }
520
- >()
431
+ const escalatedApprovals: EscalatedApprovals = new Map()
521
432
 
522
433
  let currentAssistantId: string | null = null
523
434
  let currentAssistantTimestamp: Date | null = null
@@ -526,6 +437,10 @@ export function processHistoricalMessagesWithErrors(
526
437
  // — carried onto the flushed message's streamSeq for per-role merge coverage.
527
438
  let currentAssistantStreamSeq: number | undefined
528
439
 
440
+ /**
441
+ * Flush the current assistant message to processedMessages.
442
+ * Uses the LAST message ID in the group for stable React keys across page boundaries.
443
+ */
529
444
  const flushAssistantMessage = () => {
530
445
  const idToUse = lastAssistantId || currentAssistantId
531
446
  if (idToUse && accumulator.hasContent()) {
@@ -556,6 +471,7 @@ export function processHistoricalMessagesWithErrors(
556
471
  }
557
472
 
558
473
  messages.forEach((msg, index) => {
474
+ // Filter by chat type if specified
559
475
  if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
560
476
 
561
477
  const messageDataArray = Array.isArray(msg.messageData)
@@ -619,8 +535,10 @@ export function processHistoricalMessagesWithErrors(
619
535
  }
620
536
 
621
537
  messageDataArray.forEach((data) => {
622
- processMessageData(
623
- data,
538
+ const event = decodeHistoricalMessageData(data)
539
+ if (!event) return
540
+ applyHistoryEvent(
541
+ event,
624
542
  accumulator,
625
543
  approvalStatuses,
626
544
  { displayApprovalTypes, batchApprovalsEnabled },
@@ -628,6 +546,7 @@ export function processHistoricalMessagesWithErrors(
628
546
  )
629
547
  })
630
548
 
549
+ // Check if we should flush (next message is from user or last message)
631
550
  const nextMsg = messages[index + 1]
632
551
  const isLastMessage = index === messages.length - 1
633
552
  const nextIsFromUser =
@@ -660,3 +579,52 @@ export function processHistoricalMessagesWithErrors(
660
579
  escalatedApprovals: escalatedApprovals
661
580
  }
662
581
  }
582
+
583
+ /**
584
+ * Extract error messages from historical messages
585
+ * Returns a separate array of error messages that should be displayed
586
+ */
587
+ export function extractErrorMessages(
588
+ messages: HistoricalMessage[],
589
+ options: MessageProcessingOptions = {}
590
+ ): ProcessedMessage[] {
591
+ const { assistantName = 'Fae', assistantType = 'fae', assistantAvatar, chatTypeFilter } = options
592
+
593
+ const errorMessages: ProcessedMessage[] = []
594
+
595
+ messages.forEach((msg) => {
596
+ if (chatTypeFilter && msg.chatType !== chatTypeFilter) return
597
+
598
+ const messageDataArray = Array.isArray(msg.messageData)
599
+ ? msg.messageData
600
+ : msg.messageData
601
+ ? [msg.messageData]
602
+ : []
603
+
604
+ messageDataArray.forEach((data) => {
605
+ if (data.type === MESSAGE_TYPE.ERROR) {
606
+ errorMessages.push({
607
+ id: `${msg.id}-error`,
608
+ role: 'error',
609
+ content: 'error' in data && data.error ? data.error : 'An error occurred',
610
+ name: assistantName,
611
+ assistantType,
612
+ timestamp: new Date(msg.createdAt),
613
+ avatar: assistantAvatar,
614
+ })
615
+ }
616
+ })
617
+ })
618
+
619
+ return errorMessages
620
+ }
621
+
622
+ /**
623
+ * Process messages and include error messages in the correct order.
624
+ *
625
+ * HISTORICAL ALIAS: this was a byte-identical second copy of
626
+ * `processHistoricalMessages` (both goldens snapshot identical output on the
627
+ * same corpus). The duplicate envelope was deleted in Phase 3 — kept as an
628
+ * alias for the established import sites.
629
+ */
630
+ export const processHistoricalMessagesWithErrors = processHistoricalMessages